public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v10 2/5] Allow CLUSTER and VACUUM FULL to change tablespace.
99+ messages / 8 participants
[nested] [flat]
* [PATCH v10 2/5] Allow CLUSTER and VACUUM FULL to change tablespace.
@ 2018-12-21 11:54 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2018-12-21 11:54 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 11 ++++-
doc/src/sgml/ref/vacuum.sgml | 13 +++++-
src/backend/commands/cluster.c | 28 +++++++----
src/backend/commands/tablecmds.c | 57 +++++++++++++----------
src/backend/commands/vacuum.c | 39 ++++++++++++++--
src/backend/parser/gram.y | 42 +++++++++++++++--
src/include/commands/cluster.h | 2 +-
src/include/commands/defrem.h | 18 +++----
src/include/commands/tablecmds.h | 2 +
src/include/commands/vacuum.h | 2 +
src/include/nodes/parsenodes.h | 2 +
src/test/regress/input/tablespace.source | 27 ++++++++---
src/test/regress/output/tablespace.source | 43 ++++++++++++-----
13 files changed, 219 insertions(+), 67 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 4da60d8d56..ebf23b8434 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CLUSTER [VERBOSE] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ]
+CLUSTER [VERBOSE] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ] [ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable> ]
CLUSTER [VERBOSE]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +99,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The name of the specific tablespace to store clustered relations.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 846056a353..e688edb06d 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -22,7 +22,8 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable> ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
+VACUUM ( FULL [, ...] ) [ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable> ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
@@ -299,6 +300,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The name of the specific tablespace to write new copy of the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9e5b28903a..0e13c5192e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -67,10 +68,10 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
- bool verbose, bool *pSwapToastByContent,
- TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
+ bool verbose, bool *pSwapToastByContent,
+ TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
static List *get_tables_to_cluster(MemoryContext cluster_context);
@@ -101,6 +102,13 @@ static List *get_tables_to_cluster(MemoryContext cluster_context);
void
cluster(ClusterStmt *stmt, bool isTopLevel)
{
+ /* Oid of tablespace to use for clustered relation. */
+ Oid tableSpaceOid = InvalidOid;
+
+ /* Select tablespace Oid to use. */
+ if (stmt->tablespacename)
+ tableSpaceOid = get_tablespace_oid(stmt->tablespacename, false);
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -182,7 +190,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tableSpaceOid, stmt->options);
}
else
{
@@ -230,7 +238,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tableSpaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -262,7 +270,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
* and error messages should refer to the operation as VACUUM not CLUSTER.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tableSpaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -425,7 +433,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tableSpaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -584,7 +592,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -595,6 +603,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new TeableSpace if passed. */
+ if (OidIsValid(NewTableSpaceOid))
+ tableSpace = NewTableSpaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 30ac18c0ce..ed8e405618 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12861,30 +12861,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")));
+ check_relation_is_movable(rel, newTableSpace);
reltoastrelid = rel->rd_rel->reltoastrelid;
/* Fetch the list of indexes on toast relation if necessary */
@@ -13481,6 +13458,38 @@ CreateInheritance(Relation child_rel, Relation parent_rel)
table_close(catalogRelation, RowExclusiveLock);
}
+/*
+ * Validate that relation can be moved to the specified tablespace.
+ */
+extern void
+check_relation_is_movable(Relation rel, Oid tablespaceOid)
+{
+ /*
+ * 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 (tablespaceOid == 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")));
+}
+
/*
* Obtain the source-text form of the constraint expression for a check
* constraint, given its pg_constraint tuple
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17bf4..cfa64b80fd 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,6 +31,7 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
@@ -38,6 +39,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -107,6 +109,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool parallel_option = false;
ListCell *lc;
+ Oid tableSpaceOid = InvalidOid; /* Oid of tablespace to use for relations
+ * store after VACUUM FULL. */
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -241,6 +245,18 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.multixact_freeze_table_age = -1;
}
+ /* Get tablespace Oid to use. */
+ if (vacstmt->tablespacename)
+ {
+ if (params.options & VACOPT_FULL)
+ tableSpaceOid = get_tablespace_oid(vacstmt->tablespacename, false);
+ else
+ ereport(ERROR,
+ (errmsg("incompatible SET TABLESPACE option"),
+ errdetail("You can only use SET TABLESPACE with VACUUM FULL.")));
+ }
+ params.tablespace_oid = tableSpaceOid;
+
/* user-invoked vacuum is never "for wraparound" */
params.is_wraparound = false;
@@ -1672,8 +1688,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ new_tablespaceoid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1807,6 +1824,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces.
+ */
+ if (params->options & VACOPT_FULL && OidIsValid(params->tablespace_oid) && IsSystemRelation(onerel))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ {
+ new_tablespaceoid = params->tablespace_oid;
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1876,7 +1909,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, new_tablespaceoid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index df8f63f989..1f388017be 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10589,14 +10589,14 @@ CreateConversionStmt:
/*****************************************************************************
*
* QUERY:
- * CLUSTER [VERBOSE] <qualified_name> [ USING <index_name> ]
- * CLUSTER [VERBOSE]
+ * CLUSTER [VERBOSE] <qualified_name> [ USING <index_name> ] [ TABLESPACE <tablespace_name> ]
+ * CLUSTER [VERBOSE] [ TABLESPACE <tablespace_name> ]
* CLUSTER [VERBOSE] <index_name> ON <qualified_name> (for pre-8.3)
*
*****************************************************************************/
ClusterStmt:
- CLUSTER opt_verbose qualified_name cluster_index_specification
+ CLUSTER opt_verbose qualified_name cluster_index_specification OptTableSpace
{
ClusterStmt *n = makeNode(ClusterStmt);
n->relation = $3;
@@ -10604,6 +10604,7 @@ ClusterStmt:
n->options = 0;
if ($2)
n->options |= CLUOPT_VERBOSE;
+ n->tablespacename = $5;
$$ = (Node*)n;
}
| CLUSTER opt_verbose
@@ -10625,6 +10626,7 @@ ClusterStmt:
n->options = 0;
if ($2)
n->options |= CLUOPT_VERBOSE;
+ n->tablespacename = NULL;
$$ = (Node*)n;
}
;
@@ -10639,6 +10641,8 @@ cluster_index_specification:
*
* QUERY:
* VACUUM
+ * VACUUM FULL [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
+ * VACUUM (FULL) [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
* ANALYZE
*
*****************************************************************************/
@@ -10661,6 +10665,28 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose opt_analyze opt_vacuum_relati
makeDefElem("analyze", NULL, @5));
n->rels = $6;
n->is_vacuumcmd = true;
+ n->tablespacename = NULL;
+ $$ = (Node *)n;
+ }
+ | VACUUM opt_full opt_freeze opt_verbose opt_analyze TABLESPACE name opt_vacuum_relation_list
+ {
+ VacuumStmt *n = makeNode(VacuumStmt);
+ n->options = NIL;
+ if ($2)
+ n->options = lappend(n->options,
+ makeDefElem("full", NULL, @2));
+ if ($3)
+ n->options = lappend(n->options,
+ makeDefElem("freeze", NULL, @3));
+ if ($4)
+ n->options = lappend(n->options,
+ makeDefElem("verbose", NULL, @4));
+ if ($5)
+ n->options = lappend(n->options,
+ makeDefElem("analyze", NULL, @5));
+ n->tablespacename = $7;
+ n->rels = $8;
+ n->is_vacuumcmd = true;
$$ = (Node *)n;
}
| VACUUM '(' vac_analyze_option_list ')' opt_vacuum_relation_list
@@ -10669,6 +10695,16 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose opt_analyze opt_vacuum_relati
n->options = $3;
n->rels = $5;
n->is_vacuumcmd = true;
+ n->tablespacename = NULL;
+ $$ = (Node *) n;
+ }
+ | VACUUM '(' vac_analyze_option_list ')' TABLESPACE name opt_vacuum_relation_list
+ {
+ VacuumStmt *n = makeNode(VacuumStmt);
+ n->options = $3;
+ n->tablespacename = $6;
+ n->rels = $7;
+ n->is_vacuumcmd = true;
$$ = (Node *) n;
}
;
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index e05884781b..ab7a0ad642 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -19,7 +19,7 @@
extern void cluster(ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tableSpaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index c3a906b387..0eb9f99120 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -25,15 +25,15 @@ extern void RemoveObjects(DropStmt *stmt);
/* commands/indexcmds.c */
extern ObjectAddress DefineIndex(Oid relationId,
- IndexStmt *stmt,
- Oid indexRelationId,
- Oid parentIndexId,
- Oid parentConstraintId,
- bool is_alter_table,
- bool check_rights,
- bool check_not_in_use,
- bool skip_build,
- bool quiet);
+ IndexStmt *stmt,
+ Oid indexRelationId,
+ Oid parentIndexId,
+ Oid parentConstraintId,
+ bool is_alter_table,
+ bool check_rights,
+ bool check_not_in_use,
+ bool skip_build,
+ bool quiet);
extern void ReindexIndex(RangeVar *indexRelation, char *newTableSpaceName, int options, bool concurrent);
extern Oid ReindexTable(RangeVar *relation, char *newTableSpaceName, int options, bool concurrent);
extern void ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index c1581ad178..21f9cef535 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -55,6 +55,8 @@ extern void AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
extern void CheckTableNotInUse(Relation rel, const char *stmt);
+extern void check_relation_is_movable(Relation rel, Oid tablespaceOid);
+
extern void ExecuteTruncate(TruncateStmt *stmt);
extern void ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
DropBehavior behavior, bool restart_seqs);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index c27d255d8d..8d9e862cd6 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace Oid to use for store relations
+ * after VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5075a79bd3..5fb0f72141 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3202,6 +3202,7 @@ typedef struct ClusterStmt
NodeTag type;
RangeVar *relation; /* relation being indexed, or NULL if all */
char *indexname; /* original index defined */
+ char *tablespacename; /* tablespace name to use for clustered relation. */
int options; /* OR of ClusterOption flags */
} ClusterStmt;
@@ -3218,6 +3219,7 @@ typedef struct VacuumStmt
List *options; /* list of DefElem nodes */
List *rels; /* list of VacuumRelation, or NIL for all */
bool is_vacuumcmd; /* true for VACUUM, false for ANALYZE */
+ char *tablespacename; /* tablespace name to use for vacuumed relation. */
} VacuumStmt;
/*
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 7d698d4294..8ed97afb9c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -45,23 +45,38 @@ WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspa
REINDEX INDEX regress_tblspace_test_tbl_idx TABLESPACE regress_tblspace; -- ok
REINDEX TABLE regress_tblspace_test_tbl TABLESPACE regress_tblspace; -- ok
REINDEX TABLE pg_authid TABLESPACE regress_tblspace; -- fail
-REINDEX SYSTEM CONCURRENTLY postgres TABLESPACE regress_tblspace; -- fail
-REINDEX TABLE CONCURRENTLY pg_am TABLESPACE regress_tblspace; -- fail
+REINDEX SCHEMA pg_catalog TABLESPACE regress_tblspace; -- fail
+REINDEX DATABASE postgres TABLESPACE regress_tblspace; -- fail
+REINDEX SYSTEM postgres TABLESPACE regress_tblspace; -- fail
REINDEX INDEX regress_tblspace_test_tbl_idx TABLESPACE pg_global; -- fail
-REINDEX TABLE pg_am TABLESPACE regress_tblspace; -- ok
+
+REINDEX SCHEMA pg_catalog TABLESPACE regress_tblspace; -- fail
+REINDEX DATABASE postgres TABLESPACE regress_tblspace; -- fail
+REINDEX SYSTEM postgres TABLESPACE regress_tblspace; -- fail
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE regress_tblspace; -- ok
+CLUSTER pg_authid USING pg_authid_rolname_index TABLESPACE regress_tblspace; -- fail
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL) TABLESPACE regress_tblspace pg_authid; -- skip with warning
+VACUUM (ANALYSE) TABLESPACE regress_tblspace; -- fail
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+AND relname IN ('regress_tblspace_test_tbl_idx', 'regress_tblspace_test_tbl')
ORDER BY relname;
-- move back to pg_default tablespace
REINDEX TABLE CONCURRENTLY regress_tblspace_test_tbl TABLESPACE pg_default; -- ok
-REINDEX TABLE pg_am TABLESPACE pg_default; -- ok
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE pg_default; -- ok
+VACUUM (FULL, ANALYSE, FREEZE) TABLESPACE pg_default regress_tblspace_test_tbl; -- ok
-- check that all relations moved back to pg_default
SELECT relname FROM pg_class
-WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace');
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+AND relname IN ('regress_tblspace_test_tbl_idx', 'regress_tblspace_test_tbl');
-- create a schema we can use
CREATE SCHEMA testschema;
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index bd17feaa73..5634cf20ff 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -33,6 +33,7 @@ REINDEX TABLE regress_tblspace_test_tbl TABLESPACE regress_tblspace;
ROLLBACK;
BEGIN;
REINDEX TABLE pg_am TABLESPACE regress_tblspace;
+ERROR: cannot move system relation "pg_am_name_index"
ROLLBACK;
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace');
@@ -56,30 +57,50 @@ REINDEX INDEX regress_tblspace_test_tbl_idx TABLESPACE regress_tblspace; -- ok
REINDEX TABLE regress_tblspace_test_tbl TABLESPACE regress_tblspace; -- ok
REINDEX TABLE pg_authid TABLESPACE regress_tblspace; -- fail
ERROR: cannot move system relation "pg_authid_rolname_index"
-REINDEX SYSTEM CONCURRENTLY postgres TABLESPACE regress_tblspace; -- fail
-ERROR: cannot reindex system catalogs concurrently
-REINDEX TABLE CONCURRENTLY pg_am TABLESPACE regress_tblspace; -- fail
-ERROR: cannot reindex system catalogs concurrently
+REINDEX SCHEMA pg_catalog TABLESPACE regress_tblspace; -- fail
+WARNING: cannot change tablespace of indexes for mapped relations, skipping all
+REINDEX DATABASE postgres TABLESPACE regress_tblspace; -- fail
+ERROR: can only reindex the currently open database
+REINDEX SYSTEM postgres TABLESPACE regress_tblspace; -- fail
+ERROR: can only reindex the currently open database
REINDEX INDEX regress_tblspace_test_tbl_idx TABLESPACE pg_global; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
-REINDEX TABLE pg_am TABLESPACE regress_tblspace; -- ok
+REINDEX SCHEMA pg_catalog TABLESPACE regress_tblspace; -- fail
+WARNING: cannot change tablespace of indexes for mapped relations, skipping all
+REINDEX DATABASE postgres TABLESPACE regress_tblspace; -- fail
+ERROR: can only reindex the currently open database
+REINDEX SYSTEM postgres TABLESPACE regress_tblspace; -- fail
+ERROR: can only reindex the currently open database
+-- check CLUSTER with TABLESPACE change
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE regress_tblspace; -- ok
+CLUSTER pg_authid USING pg_authid_rolname_index TABLESPACE regress_tblspace; -- fail
+ERROR: cannot cluster a shared catalog
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL) TABLESPACE regress_tblspace pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE) TABLESPACE regress_tblspace; -- fail
+ERROR: incompatible SET TABLESPACE option
+DETAIL: You can only use SET TABLESPACE with VACUUM FULL.
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+AND relname IN ('regress_tblspace_test_tbl_idx', 'regress_tblspace_test_tbl')
ORDER BY relname;
relname
-------------------------------
- pg_am_name_index
- pg_am_oid_index
+ regress_tblspace_test_tbl
regress_tblspace_test_tbl_idx
-(3 rows)
+(2 rows)
-- move back to pg_default tablespace
REINDEX TABLE CONCURRENTLY regress_tblspace_test_tbl TABLESPACE pg_default; -- ok
-REINDEX TABLE pg_am TABLESPACE pg_default; -- ok
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE pg_default; -- ok
+VACUUM (FULL, ANALYSE, FREEZE) TABLESPACE pg_default regress_tblspace_test_tbl; -- ok
-- check that all relations moved back to pg_default
SELECT relname FROM pg_class
-WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace');
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+AND relname IN ('regress_tblspace_test_tbl_idx', 'regress_tblspace_test_tbl');
relname
---------
(0 rows)
--
2.17.0
--17pEHd4RhPHOinZp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v10-0003-Capitalize-consistently.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v16 4/7] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 11 ++++-
doc/src/sgml/ref/vacuum.sgml | 11 +++++
src/backend/commands/cluster.c | 42 ++++++++++++++++---
src/backend/commands/vacuum.c | 51 +++++++++++++++++++++--
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 1 +
src/backend/parser/gram.y | 38 ++++++++++++++++-
src/backend/postmaster/autovacuum.c | 1 +
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/include/nodes/parsenodes.h | 2 +
src/test/regress/input/tablespace.source | 23 +++++++++-
src/test/regress/output/tablespace.source | 37 +++++++++++++++-
13 files changed, 208 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index bd0682ddfd..4847837765 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CLUSTER [VERBOSE] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ]
+CLUSTER [VERBOSE] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ] [ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable> ]
CLUSTER [VERBOSE]
</synopsis>
</refsynopsisdiv>
@@ -108,6 +108,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The name of a specific tablespace to store clustered relations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 846056a353..0a00125a36 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -23,6 +23,7 @@ PostgreSQL documentation
<synopsis>
VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
+VACUUM ( FULL [, ...] ) [ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable> ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
@@ -299,6 +300,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The name of a specific tablespace to write a new copy of the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 0f25576a32..7c52bb470f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -201,7 +203,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -249,7 +251,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -281,7 +283,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* and error messages should refer to the operation as VACUUM not CLUSTER.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -394,6 +396,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -444,7 +463,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -603,7 +622,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -614,6 +633,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1058,6 +1081,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 59731d687f..95bec8d6ba 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
bool parallel_option = false;
ListCell *lc;
+ Oid tablespaceOid = InvalidOid; /* Oid of tablespace to use for relations
+ * after VACUUM FULL. */
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
@@ -241,6 +246,28 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.multixact_freeze_table_age = -1;
}
+ /* Get tablespace Oid to use. */
+ if (vacstmt->tablespacename)
+ {
+ if (params.options & VACOPT_FULL)
+ {
+ tablespaceOid = get_tablespace_oid(vacstmt->tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ vacstmt->tablespacename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+ }
+ params.tablespace_oid = tablespaceOid;
+
/* user-invoked vacuum is never "for wraparound" */
params.is_wraparound = false;
@@ -1672,8 +1699,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1807,6 +1835,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1876,7 +1921,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a2aa687771..e04ee775c2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3903,6 +3903,7 @@ _copyVacuumStmt(const VacuumStmt *from)
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(rels);
COPY_SCALAR_FIELD(is_vacuumcmd);
+ COPY_STRING_FIELD(tablespacename);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3967d0ce08..9ffad8be90 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1703,6 +1703,7 @@ _equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(rels);
COMPARE_SCALAR_FIELD(is_vacuumcmd);
+ COMPARE_SCALAR_FIELD(tablespacename);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f1ec2b4951..a91617c4e2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10626,7 +10626,7 @@ CreateConversionStmt:
*****************************************************************************/
ClusterStmt:
- CLUSTER opt_verbose qualified_name cluster_index_specification
+ CLUSTER opt_verbose qualified_name cluster_index_specification OptTableSpace
{
ClusterStmt *n = makeNode(ClusterStmt);
n->relation = $3;
@@ -10683,6 +10683,8 @@ cluster_index_specification:
*
* QUERY:
* VACUUM
+ * VACUUM FULL [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
+ * VACUUM (FULL) [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
* ANALYZE
*
*****************************************************************************/
@@ -10705,6 +10707,28 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose opt_analyze opt_vacuum_relati
makeDefElem("analyze", NULL, @5));
n->rels = $6;
n->is_vacuumcmd = true;
+ n->tablespacename = NULL;
+ $$ = (Node *)n;
+ }
+ | VACUUM opt_full opt_freeze opt_verbose opt_analyze TABLESPACE name opt_vacuum_relation_list
+ {
+ VacuumStmt *n = makeNode(VacuumStmt);
+ n->options = NIL;
+ if ($2)
+ n->options = lappend(n->options,
+ makeDefElem("full", NULL, @2));
+ if ($3)
+ n->options = lappend(n->options,
+ makeDefElem("freeze", NULL, @3));
+ if ($4)
+ n->options = lappend(n->options,
+ makeDefElem("verbose", NULL, @4));
+ if ($5)
+ n->options = lappend(n->options,
+ makeDefElem("analyze", NULL, @5));
+ n->tablespacename = $7;
+ n->rels = $8;
+ n->is_vacuumcmd = true;
$$ = (Node *)n;
}
| VACUUM '(' vac_analyze_option_list ')' opt_vacuum_relation_list
@@ -10713,6 +10737,16 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose opt_analyze opt_vacuum_relati
n->options = $3;
n->rels = $5;
n->is_vacuumcmd = true;
+ n->tablespacename = NULL;
+ $$ = (Node *) n;
+ }
+ | VACUUM '(' vac_analyze_option_list ')' TABLESPACE name opt_vacuum_relation_list
+ {
+ VacuumStmt *n = makeNode(VacuumStmt);
+ n->options = $3;
+ n->tablespacename = $6;
+ n->rels = $7;
+ n->is_vacuumcmd = true;
$$ = (Node *) n;
}
;
@@ -10726,6 +10760,7 @@ AnalyzeStmt: analyze_keyword opt_verbose opt_vacuum_relation_list
makeDefElem("verbose", NULL, @2));
n->rels = $3;
n->is_vacuumcmd = false;
+ n->tablespacename = NULL;
$$ = (Node *)n;
}
| analyze_keyword '(' vac_analyze_option_list ')' opt_vacuum_relation_list
@@ -10734,6 +10769,7 @@ AnalyzeStmt: analyze_keyword opt_verbose opt_vacuum_relation_list
n->options = $3;
n->rels = $5;
n->is_vacuumcmd = false;
+ n->tablespacename = NULL;
$$ = (Node *) n;
}
;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffab27..8acc321faa 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2897,6 +2897,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 2779bea5c9..6758e9812f 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace Oid to use for relations
+ * after VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 21b1f51950..2236aaa2dc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3203,6 +3203,7 @@ typedef struct ClusterStmt
NodeTag type;
RangeVar *relation; /* relation being indexed, or NULL if all */
char *indexname; /* original index defined */
+ char *tablespacename; /* tablespace name to use for clustered relation */
int options; /* OR of ClusterOption flags */
List *params; /* Params not further parsed by the grammar */
} ClusterStmt;
@@ -3220,6 +3221,7 @@ typedef struct VacuumStmt
List *options; /* list of DefElem nodes */
List *rels; /* list of VacuumRelation, or NIL for all */
bool is_vacuumcmd; /* true for VACUUM, false for ANALYZE */
+ char *tablespacename; /* tablespace name to use for vacuumed relation */
} VacuumStmt;
/*
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 57715b3cca..03c5fb9e9e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -41,11 +41,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE regress_tblspace; -- ok
+CLUSTER pg_authid USING pg_authid_rolname_index TABLESPACE regress_tblspace; -- fail
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE pg_global; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE) TABLESPACE pg_default regress_tblspace_test_tbl; -- ok
+VACUUM (FULL) TABLESPACE pg_default pg_authid; -- skip with warning
+VACUUM (ANALYSE) TABLESPACE pg_default; -- fail
+VACUUM (FULL) TABLESPACE pg_global regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 7733254416..c27fa70143 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -50,9 +50,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE regress_tblspace; -- ok
+CLUSTER pg_authid USING pg_authid_rolname_index TABLESPACE regress_tblspace; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE pg_global; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE) TABLESPACE pg_default regress_tblspace_test_tbl; -- ok
+VACUUM (FULL) TABLESPACE pg_default pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE) TABLESPACE pg_default; -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL) TABLESPACE pg_global regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--wayzTnRSUXKNfBqd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v16-0005-fixes2.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v26 3/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 66 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 55 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 +++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 226 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index e6ebce27e6..b198a7605c 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -95,6 +96,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -126,6 +136,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9419cc4dcf..3990dbaca5 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -69,7 +71,8 @@ typedef struct
static void rebuild_relation(Relation OldHeap, Oid indexOid,
- bool isTopLevel, bool verbose);
+ bool isTopLevel, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool isTopLevel, bool verbose,
bool *pSwapToastByContent,
@@ -107,6 +110,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
int options = 0;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -118,6 +124,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
options |= CLUOPT_VERBOSE;
else
options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -126,6 +134,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -195,7 +216,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, options, isTopLevel);
+ cluster_rel(tableOid, indexOid, options, isTopLevel,
+ tablespaceOid);
}
else
{
@@ -245,7 +267,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* Do the job. */
cluster_rel(rvtc->tableOid, rvtc->indexOid,
options | CLUOPT_RECHECK,
- isTopLevel);
+ isTopLevel, tablespaceOid);
PopActiveSnapshot();
CommitTransactionCommand();
}
@@ -274,9 +296,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
+cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel, Oid tablespaceOid)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -376,6 +401,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -426,7 +468,8 @@ cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, isTopLevel, verbose);
+ rebuild_relation(OldHeap, indexOid, isTopLevel, verbose,
+ tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -576,7 +619,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -587,6 +630,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1032,6 +1079,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 708f0d55cd..308a4ae7ec 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13122,8 +13122,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ddeec870d8..25e437056c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1724,8 +1754,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1861,6 +1892,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1930,7 +1978,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options, true);
+ cluster_rel(relid, InvalidOid, cluster_options, true,
+ tablespaceOid);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0256da5530..87b2042d4c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10438,8 +10438,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1b8cd7bacd..e231ce81ec 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2900,6 +2900,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5d2cd88de9..7c5ba238be 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2284,7 +2284,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3711,9 +3713,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index c761b9575a..9772aa1761 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -21,7 +21,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
- bool isTopLevel);
+ bool isTopLevel, Oid tablespaceOid);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index d9475c9989..b6e944d3a5 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--da4uJneut+ArUgXk
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v26-0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v20 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 846056a353..ae36808aa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a74e9ed4ad..854ceac576 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12962,8 +12962,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 3a89f8fe1e..e8a2b10497 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -107,6 +110,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool parallel_option = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -143,6 +150,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
parallel_option = true;
@@ -204,6 +213,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot specify both FULL and PARALLEL options")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1672,8 +1702,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1807,6 +1838,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1876,7 +1924,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5bbec0323e..2c425b7e59 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10666,8 +10666,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffab27..8acc321faa 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2897,6 +2897,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b0750d0b9a..5c340bf94f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2275,7 +2275,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3687,9 +3689,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 2779bea5c9..c425d971f4 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 57715b3cca..6942775bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -41,11 +41,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 7733254416..2825984394 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -50,9 +50,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--opJtzjQTFsWo+cga
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v20-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v33 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 ++++---
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 67 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 55 ++++++++++++++++++-
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 ++-
src/include/commands/cluster.h | 3 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 +++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 221 insertions(+), 25 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 1c420d02e1..002d2bf293 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
int options = 0;
bool verbose = false;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +131,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -192,7 +213,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, options);
+ cluster_rel(tableOid, indexOid, options,
+ tablespaceOid);
}
else
{
@@ -241,7 +263,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
cluster_rel(rvtc->tableOid, rvtc->indexOid,
- options | CLUOPT_RECHECK);
+ options | CLUOPT_RECHECK,
+ tablespaceOid);
PopActiveSnapshot();
CommitTransactionCommand();
}
@@ -270,9 +293,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, int options, Oid tablespaceOid)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -372,6 +398,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -422,7 +465,8 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose,
+ tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -571,7 +615,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -582,6 +626,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1026,6 +1074,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 932b8ddfd2..bbf3bad44c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13195,8 +13195,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index f1112111de..c0dda5e584 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1702,8 +1732,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1841,6 +1872,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1910,7 +1958,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, cluster_options,
+ tablespaceOid);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ecff4cd2ac..4ef6bb27eb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10445,8 +10445,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index aa5b97fbac..f364b37585 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2916,6 +2916,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 65ebf911f3..0908d7d4c7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2312,7 +2312,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3806,9 +3808,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 7cfb37c9b2..30ef24c41b 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,7 +27,8 @@ typedef enum ClusterOption
} ClusterOption;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
+ Oid tablespaceOid);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 5d8a22cffb..f4687f6bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1169f0318b..e0a5c48fe9 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--0MC1Z9bqFMhiUToQ
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v33-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v30 5/6] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 67 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 55 ++++++++++++++++++-
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 ++-
src/include/commands/cluster.h | 3 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 +++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 228 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index efd8165e35..cbfc0582be 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -95,6 +96,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -126,6 +136,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 8ababbeb14..b289a76d58 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -104,6 +107,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
int options = 0;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -115,6 +121,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
options |= CLUOPT_VERBOSE;
else
options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +131,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -192,7 +213,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, options);
+ cluster_rel(tableOid, indexOid, options,
+ tablespaceOid);
}
else
{
@@ -241,7 +263,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
cluster_rel(rvtc->tableOid, rvtc->indexOid,
- options | CLUOPT_RECHECK);
+ options | CLUOPT_RECHECK,
+ tablespaceOid);
PopActiveSnapshot();
CommitTransactionCommand();
}
@@ -270,9 +293,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, int options, Oid tablespaceOid)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -372,6 +398,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -422,7 +465,8 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose,
+ tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -571,7 +615,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -582,6 +626,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1026,6 +1074,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 932b8ddfd2..bbf3bad44c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13195,8 +13195,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 395e75f768..09356508ba 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1702,8 +1732,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1839,6 +1870,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1908,7 +1956,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, cluster_options,
+ tablespaceOid);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f5276d354c..1fecc62884 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10460,8 +10460,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index aa5b97fbac..f364b37585 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2916,6 +2916,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 65ebf911f3..0908d7d4c7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2312,7 +2312,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3806,9 +3808,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..30300a8f74 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,8 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
+ Oid tablespaceOid);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 5d8a22cffb..f4687f6bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1169f0318b..e0a5c48fe9 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--t0UkRYy7tHLRMCai
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v30-0006-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v19 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 21 +++++++-
doc/src/sgml/ref/vacuum.sgml | 21 ++++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 2 +
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 223 insertions(+), 17 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 9980c522f2..bf9a4534de 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -21,12 +21,13 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CLUSTER [VERBOSE] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ]
+CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ]
CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -114,6 +124,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 846056a353..1e0d44a6b0 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -23,6 +23,7 @@ PostgreSQL documentation
<synopsis>
VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
+VACUUM ( FULL [, ...] ) [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
@@ -35,6 +36,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +257,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +310,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 0d9f1f045f..501cb8e459 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameter not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace to use for the rebuilt relation, or
+ * InvalidOid, to use the current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b3410379ec..f8fcbc1143 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12962,8 +12962,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 3a89f8fe1e..e8a2b10497 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -107,6 +110,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool parallel_option = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -143,6 +150,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
parallel_option = true;
@@ -204,6 +213,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot specify both FULL and PARALLEL options")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1672,8 +1702,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1807,6 +1838,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1876,7 +1924,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 531a057ff3..ec3ebfc26a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10658,6 +10658,8 @@ cluster_index_specification:
*
* QUERY:
* VACUUM
+ * VACUUM FULL [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
+ * VACUUM (FULL) [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
* ANALYZE
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffab27..8acc321faa 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2897,6 +2897,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b0750d0b9a..5c340bf94f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2275,7 +2275,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3687,9 +3689,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 2779bea5c9..6758e9812f 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace Oid to use for relations
+ * after VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 57715b3cca..6942775bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -41,11 +41,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 7733254416..2825984394 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -50,9 +50,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--m972NQjnE83KvVa/
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v19-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v31 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 20 ++++---
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 67 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 55 ++++++++++++++++++-
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 ++-
src/include/commands/cluster.h | 3 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 +++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 25 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..7b4aea2ca3 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -87,6 +88,7 @@ CLUSTER [VERBOSE]
<title>Parameters</title>
<variablelist>
+ <varlistentry>
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -105,6 +107,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +126,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 1c420d02e1..002d2bf293 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
int options = 0;
bool verbose = false;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +131,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -192,7 +213,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, options);
+ cluster_rel(tableOid, indexOid, options,
+ tablespaceOid);
}
else
{
@@ -241,7 +263,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
cluster_rel(rvtc->tableOid, rvtc->indexOid,
- options | CLUOPT_RECHECK);
+ options | CLUOPT_RECHECK,
+ tablespaceOid);
PopActiveSnapshot();
CommitTransactionCommand();
}
@@ -270,9 +293,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, int options, Oid tablespaceOid)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -372,6 +398,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -422,7 +465,8 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose,
+ tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -571,7 +615,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -582,6 +626,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1026,6 +1074,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 932b8ddfd2..bbf3bad44c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13195,8 +13195,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index f1112111de..c0dda5e584 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1702,8 +1732,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1841,6 +1872,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1910,7 +1958,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, cluster_options,
+ tablespaceOid);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 61f0236041..0710eafa27 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10461,8 +10461,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index aa5b97fbac..f364b37585 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2916,6 +2916,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 65ebf911f3..0908d7d4c7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2312,7 +2312,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3806,9 +3808,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 7cfb37c9b2..30ef24c41b 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,7 +27,8 @@ typedef enum ClusterOption
} ClusterOption;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
+ Oid tablespaceOid);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 5d8a22cffb..f4687f6bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1169f0318b..e0a5c48fe9 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--2NVUe83uzBILE3UT
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v31-0001-ExecReindex-and-ReindexParams.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v25 4/6] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 66 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 55 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 +++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 226 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index e6ebce27e6..b198a7605c 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -95,6 +96,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -126,6 +136,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9419cc4dcf..3990dbaca5 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -69,7 +71,8 @@ typedef struct
static void rebuild_relation(Relation OldHeap, Oid indexOid,
- bool isTopLevel, bool verbose);
+ bool isTopLevel, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool isTopLevel, bool verbose,
bool *pSwapToastByContent,
@@ -107,6 +110,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
int options = 0;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -118,6 +124,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
options |= CLUOPT_VERBOSE;
else
options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -126,6 +134,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -195,7 +216,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, options, isTopLevel);
+ cluster_rel(tableOid, indexOid, options, isTopLevel,
+ tablespaceOid);
}
else
{
@@ -245,7 +267,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* Do the job. */
cluster_rel(rvtc->tableOid, rvtc->indexOid,
options | CLUOPT_RECHECK,
- isTopLevel);
+ isTopLevel, tablespaceOid);
PopActiveSnapshot();
CommitTransactionCommand();
}
@@ -274,9 +296,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
+cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel, Oid tablespaceOid)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -376,6 +401,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -426,7 +468,8 @@ cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, isTopLevel, verbose);
+ rebuild_relation(OldHeap, indexOid, isTopLevel, verbose,
+ tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -576,7 +619,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -587,6 +630,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1032,6 +1079,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 708f0d55cd..308a4ae7ec 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13122,8 +13122,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ddeec870d8..25e437056c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1724,8 +1754,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1861,6 +1892,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1930,7 +1978,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options, true);
+ cluster_rel(relid, InvalidOid, cluster_options, true,
+ tablespaceOid);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 525dfb8132..5ab7f7ba73 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10438,8 +10438,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1b8cd7bacd..e231ce81ec 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2900,6 +2900,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5d2cd88de9..7c5ba238be 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2284,7 +2284,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3711,9 +3713,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index c761b9575a..9772aa1761 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -21,7 +21,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
- bool isTopLevel);
+ bool isTopLevel, Oid tablespaceOid);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index d9475c9989..b6e944d3a5 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--l76fUT7nc3MelDdI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v25-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v32 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 20 ++++---
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 67 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 55 ++++++++++++++++++-
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 ++-
src/include/commands/cluster.h | 3 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 +++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 25 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..7b4aea2ca3 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -87,6 +88,7 @@ CLUSTER [VERBOSE]
<title>Parameters</title>
<variablelist>
+ <varlistentry>
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -105,6 +107,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +126,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 1c420d02e1..002d2bf293 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
int options = 0;
bool verbose = false;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +131,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -192,7 +213,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, options);
+ cluster_rel(tableOid, indexOid, options,
+ tablespaceOid);
}
else
{
@@ -241,7 +263,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
cluster_rel(rvtc->tableOid, rvtc->indexOid,
- options | CLUOPT_RECHECK);
+ options | CLUOPT_RECHECK,
+ tablespaceOid);
PopActiveSnapshot();
CommitTransactionCommand();
}
@@ -270,9 +293,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, int options, Oid tablespaceOid)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -372,6 +398,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -422,7 +465,8 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose,
+ tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -571,7 +615,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -582,6 +626,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1026,6 +1074,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 932b8ddfd2..bbf3bad44c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13195,8 +13195,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index f1112111de..c0dda5e584 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1702,8 +1732,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1841,6 +1872,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1910,7 +1958,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, cluster_options,
+ tablespaceOid);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 469de52bc2..012efc55d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10461,8 +10461,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index aa5b97fbac..f364b37585 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2916,6 +2916,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 65ebf911f3..0908d7d4c7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2312,7 +2312,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3806,9 +3808,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 7cfb37c9b2..30ef24c41b 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,7 +27,8 @@ typedef enum ClusterOption
} ClusterOption;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
+ Oid tablespaceOid);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 5d8a22cffb..f4687f6bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1169f0318b..e0a5c48fe9 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--8jEihaNHb65WmIJG
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v32-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v23 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 4 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 223 insertions(+), 19 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 110fb3083e..5274b1af7e 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -127,6 +137,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index ead2c1cd4b..e94f73414f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -69,7 +71,7 @@ typedef struct
static void rebuild_relation(Relation OldHeap, Oid indexOid,
- bool isTopLevel, bool verbose);
+ Oid NewTableSpaceOid, bool isTopLevel, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool isTopLevel, bool verbose,
bool *pSwapToastByContent,
@@ -107,6 +109,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
int options = 0;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -118,6 +123,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
options |= CLUOPT_VERBOSE;
else
options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -126,6 +133,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -195,7 +215,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, options, isTopLevel);
+ cluster_rel(tableOid, indexOid, tablespaceOid, options, isTopLevel);
}
else
{
@@ -243,7 +263,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
options | CLUOPT_RECHECK,
isTopLevel);
PopActiveSnapshot();
@@ -274,9 +294,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options, bool isTopLevel)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -376,6 +399,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -426,7 +466,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, isTopLevel, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, isTopLevel, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -576,7 +616,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool isTopLevel, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -587,6 +627,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1032,6 +1076,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cc5fd46a1d..c1f5054042 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13122,8 +13122,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ddeec870d8..07d50bea0d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1724,8 +1754,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1861,6 +1892,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1930,7 +1978,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options, true);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options, true);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dbf0e0b0f5..a3ab17ba96 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10440,8 +10440,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1b8cd7bacd..e231ce81ec 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2900,6 +2900,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index c761b9575a..a64b7e84da 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,8 +20,8 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
- bool isTopLevel);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid,
+ int options, bool isTopLevel);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index d9475c9989..b6e944d3a5 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--mP3DRpeJDSE+ciuQ
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v23-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v34 6/8] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 ++++---
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 67 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 55 ++++++++++++++++++-
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 ++-
src/include/commands/cluster.h | 3 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 +++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 221 insertions(+), 25 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 272723e050..39b32bb85f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
int options = 0;
bool verbose = false;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +131,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -192,7 +213,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, options);
+ cluster_rel(tableOid, indexOid, options,
+ tablespaceOid);
}
else
{
@@ -241,7 +263,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
cluster_rel(rvtc->tableOid, rvtc->indexOid,
- options | CLUOPT_RECHECK);
+ options | CLUOPT_RECHECK,
+ tablespaceOid);
PopActiveSnapshot();
CommitTransactionCommand();
}
@@ -270,9 +293,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, int options, Oid tablespaceOid)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -372,6 +398,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -422,7 +465,8 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose,
+ tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -571,7 +615,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -582,6 +626,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1026,6 +1074,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bb96e330d4..3fb726db63 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 98270a1049..6861e948f2 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1888,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1926,7 +1974,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, cluster_options,
+ tablespaceOid);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8f341ac006..2ef9485ab2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10444,8 +10444,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e28944d2f..d2a443ebdb 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2933,6 +2933,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 65ebf911f3..0908d7d4c7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2312,7 +2312,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3806,9 +3808,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 7cfb37c9b2..30ef24c41b 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,7 +27,8 @@ typedef enum ClusterOption
} ClusterOption;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
+ Oid tablespaceOid);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 5d8a22cffb..f4687f6bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1169f0318b..19c9504c05 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--ucW7QTJbHsz5ya91
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v34-0007-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v29 5/7] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 67 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 55 ++++++++++++++++++-
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 ++-
src/include/commands/cluster.h | 3 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 +++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 228 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index efd8165e35..cbfc0582be 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -95,6 +96,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -126,6 +136,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 8ababbeb14..b289a76d58 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -104,6 +107,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
int options = 0;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -115,6 +121,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
options |= CLUOPT_VERBOSE;
else
options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +131,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -192,7 +213,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, options);
+ cluster_rel(tableOid, indexOid, options,
+ tablespaceOid);
}
else
{
@@ -241,7 +263,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
cluster_rel(rvtc->tableOid, rvtc->indexOid,
- options | CLUOPT_RECHECK);
+ options | CLUOPT_RECHECK,
+ tablespaceOid);
PopActiveSnapshot();
CommitTransactionCommand();
}
@@ -270,9 +293,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, int options, Oid tablespaceOid)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -372,6 +398,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -422,7 +465,8 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose,
+ tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -571,7 +615,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -582,6 +626,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1026,6 +1074,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e74f0dd80a..0461332e96 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13160,8 +13160,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 1b6717f727..6a8116dfa3 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1702,8 +1732,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1839,6 +1870,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1908,7 +1956,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, cluster_options,
+ tablespaceOid);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ea371b8d6b..488a4c44bc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10462,8 +10462,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 2cef56f115..e73e124335 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2916,6 +2916,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ebaca25494..173848f92f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2287,7 +2287,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3734,9 +3736,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..30300a8f74 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,8 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
+ Oid tablespaceOid);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 5d8a22cffb..f4687f6bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1169f0318b..e0a5c48fe9 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--k1lZvvs/B4yU6o8G
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v29-0006-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v15 2/6] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 11 ++++-
doc/src/sgml/ref/vacuum.sgml | 11 +++++
src/backend/commands/cluster.c | 58 ++++++++++++++++++++---
src/backend/commands/vacuum.c | 51 ++++++++++++++++++--
src/backend/nodes/copyfuncs.c | 2 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/parser/gram.y | 45 ++++++++++++++++--
src/backend/postmaster/autovacuum.c | 1 +
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/include/nodes/parsenodes.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
13 files changed, 231 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 4da60d8d56..1adf12c3b9 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CLUSTER [VERBOSE] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ]
+CLUSTER [VERBOSE] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ] [ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable> ]
CLUSTER [VERBOSE]
</synopsis>
</refsynopsisdiv>
@@ -99,6 +99,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The name of a specific tablespace to store clustered relations.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 846056a353..0a00125a36 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -23,6 +23,7 @@ PostgreSQL documentation
<synopsis>
VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
+VACUUM ( FULL [, ...] ) [ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable> ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
@@ -299,6 +300,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The name of a specific tablespace to write a new copy of the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index afc752e9d6..894b1fb98f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,10 +33,12 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -67,7 +69,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -101,6 +103,22 @@ static List *get_tables_to_cluster(MemoryContext cluster_context);
void
cluster(ClusterStmt *stmt, bool isTopLevel)
{
+ /* Oid of tablespace to use for clustered relation. */
+ Oid tablespaceOid = InvalidOid;
+
+ /* Select tablespace Oid to use. */
+ if (stmt->tablespacename)
+ {
+ tablespaceOid = get_tablespace_oid(stmt->tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ stmt->tablespacename)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -182,7 +200,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -230,7 +248,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -262,7 +280,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
* and error messages should refer to the operation as VACUUM not CLUSTER.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -375,6 +393,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -425,7 +460,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -584,7 +619,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -595,6 +630,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1039,6 +1078,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 59731d687f..95bec8d6ba 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
bool parallel_option = false;
ListCell *lc;
+ Oid tablespaceOid = InvalidOid; /* Oid of tablespace to use for relations
+ * after VACUUM FULL. */
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
@@ -241,6 +246,28 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.multixact_freeze_table_age = -1;
}
+ /* Get tablespace Oid to use. */
+ if (vacstmt->tablespacename)
+ {
+ if (params.options & VACOPT_FULL)
+ {
+ tablespaceOid = get_tablespace_oid(vacstmt->tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ vacstmt->tablespacename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+ }
+ params.tablespace_oid = tablespaceOid;
+
/* user-invoked vacuum is never "for wraparound" */
params.is_wraparound = false;
@@ -1672,8 +1699,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1807,6 +1835,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1876,7 +1921,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9fe820f691..55ff3e8419 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3317,6 +3317,7 @@ _copyClusterStmt(const ClusterStmt *from)
COPY_NODE_FIELD(relation);
COPY_STRING_FIELD(indexname);
COPY_SCALAR_FIELD(options);
+ COPY_STRING_FIELD(tablespacename);
return newnode;
}
@@ -3902,6 +3903,7 @@ _copyVacuumStmt(const VacuumStmt *from)
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(rels);
COPY_SCALAR_FIELD(is_vacuumcmd);
+ COPY_STRING_FIELD(tablespacename);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e87a696471..baee0b944a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1215,6 +1215,7 @@ _equalClusterStmt(const ClusterStmt *a, const ClusterStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_STRING_FIELD(indexname);
COMPARE_SCALAR_FIELD(options);
+ COMPARE_SCALAR_FIELD(tablespacename);
return true;
}
@@ -1702,6 +1703,7 @@ _equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(rels);
COMPARE_SCALAR_FIELD(is_vacuumcmd);
+ COMPARE_SCALAR_FIELD(tablespacename);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index beeacb9520..8ff12fe086 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10599,14 +10599,14 @@ CreateConversionStmt:
/*****************************************************************************
*
* QUERY:
- * CLUSTER [VERBOSE] <qualified_name> [ USING <index_name> ]
- * CLUSTER [VERBOSE]
+ * CLUSTER [VERBOSE] <qualified_name> [ USING <index_name> ] [ TABLESPACE <tablespace_name> ]
+ * CLUSTER [VERBOSE] [ TABLESPACE <tablespace_name> ]
* CLUSTER [VERBOSE] <index_name> ON <qualified_name> (for pre-8.3)
*
*****************************************************************************/
ClusterStmt:
- CLUSTER opt_verbose qualified_name cluster_index_specification
+ CLUSTER opt_verbose qualified_name cluster_index_specification OptTableSpace
{
ClusterStmt *n = makeNode(ClusterStmt);
n->relation = $3;
@@ -10614,6 +10614,7 @@ ClusterStmt:
n->options = 0;
if ($2)
n->options |= CLUOPT_VERBOSE;
+ n->tablespacename = $5;
$$ = (Node*)n;
}
| CLUSTER opt_verbose
@@ -10624,6 +10625,7 @@ ClusterStmt:
n->options = 0;
if ($2)
n->options |= CLUOPT_VERBOSE;
+ n->tablespacename = NULL;
$$ = (Node*)n;
}
/* kept for pre-8.3 compatibility */
@@ -10635,6 +10637,7 @@ ClusterStmt:
n->options = 0;
if ($2)
n->options |= CLUOPT_VERBOSE;
+ n->tablespacename = NULL;
$$ = (Node*)n;
}
;
@@ -10649,6 +10652,8 @@ cluster_index_specification:
*
* QUERY:
* VACUUM
+ * VACUUM FULL [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
+ * VACUUM (FULL) [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
* ANALYZE
*
*****************************************************************************/
@@ -10671,6 +10676,28 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose opt_analyze opt_vacuum_relati
makeDefElem("analyze", NULL, @5));
n->rels = $6;
n->is_vacuumcmd = true;
+ n->tablespacename = NULL;
+ $$ = (Node *)n;
+ }
+ | VACUUM opt_full opt_freeze opt_verbose opt_analyze TABLESPACE name opt_vacuum_relation_list
+ {
+ VacuumStmt *n = makeNode(VacuumStmt);
+ n->options = NIL;
+ if ($2)
+ n->options = lappend(n->options,
+ makeDefElem("full", NULL, @2));
+ if ($3)
+ n->options = lappend(n->options,
+ makeDefElem("freeze", NULL, @3));
+ if ($4)
+ n->options = lappend(n->options,
+ makeDefElem("verbose", NULL, @4));
+ if ($5)
+ n->options = lappend(n->options,
+ makeDefElem("analyze", NULL, @5));
+ n->tablespacename = $7;
+ n->rels = $8;
+ n->is_vacuumcmd = true;
$$ = (Node *)n;
}
| VACUUM '(' vac_analyze_option_list ')' opt_vacuum_relation_list
@@ -10679,6 +10706,16 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose opt_analyze opt_vacuum_relati
n->options = $3;
n->rels = $5;
n->is_vacuumcmd = true;
+ n->tablespacename = NULL;
+ $$ = (Node *) n;
+ }
+ | VACUUM '(' vac_analyze_option_list ')' TABLESPACE name opt_vacuum_relation_list
+ {
+ VacuumStmt *n = makeNode(VacuumStmt);
+ n->options = $3;
+ n->tablespacename = $6;
+ n->rels = $7;
+ n->is_vacuumcmd = true;
$$ = (Node *) n;
}
;
@@ -10692,6 +10729,7 @@ AnalyzeStmt: analyze_keyword opt_verbose opt_vacuum_relation_list
makeDefElem("verbose", NULL, @2));
n->rels = $3;
n->is_vacuumcmd = false;
+ n->tablespacename = NULL;
$$ = (Node *)n;
}
| analyze_keyword '(' vac_analyze_option_list ')' opt_vacuum_relation_list
@@ -10700,6 +10738,7 @@ AnalyzeStmt: analyze_keyword opt_verbose opt_vacuum_relation_list
n->options = $3;
n->rels = $5;
n->is_vacuumcmd = false;
+ n->tablespacename = NULL;
$$ = (Node *) n;
}
;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffab27..8acc321faa 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2897,6 +2897,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index e05884781b..aecc0c312a 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -19,7 +19,7 @@
extern void cluster(ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 2779bea5c9..6758e9812f 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace Oid to use for relations
+ * after VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b64d538671..9575a7985c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3203,6 +3203,7 @@ typedef struct ClusterStmt
NodeTag type;
RangeVar *relation; /* relation being indexed, or NULL if all */
char *indexname; /* original index defined */
+ char *tablespacename; /* tablespace name to use for clustered relation */
int options; /* OR of ClusterOption flags */
} ClusterStmt;
@@ -3219,6 +3220,7 @@ typedef struct VacuumStmt
List *options; /* list of DefElem nodes */
List *rels; /* list of VacuumRelation, or NIL for all */
bool is_vacuumcmd; /* true for VACUUM, false for ANALYZE */
+ char *tablespacename; /* tablespace name to use for vacuumed relation */
} VacuumStmt;
/*
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 77ea4cbcee..352db323c8 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -41,11 +41,32 @@ REINDEX TABLE CONCURRENTLY pg_am TABLESPACE regress_tblspace; -- fail
REINDEX INDEX regress_tblspace_test_tbl_idx TABLESPACE pg_global; -- fail
REINDEX TABLE pg_am TABLESPACE regress_tblspace; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE regress_tblspace; -- ok
+CLUSTER pg_authid USING pg_authid_rolname_index TABLESPACE regress_tblspace; -- fail
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE pg_global; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE) TABLESPACE pg_default regress_tblspace_test_tbl; -- ok
+VACUUM (FULL) TABLESPACE pg_default pg_authid; -- skip with warning
+VACUUM (ANALYSE) TABLESPACE pg_default; -- fail
+VACUUM (FULL) TABLESPACE pg_global regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX TABLE CONCURRENTLY regress_tblspace_test_tbl TABLESPACE pg_default; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0db9929f44..a05176069b 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -50,9 +50,44 @@ REINDEX INDEX regress_tblspace_test_tbl_idx TABLESPACE pg_global; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX TABLE pg_am TABLESPACE regress_tblspace; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE regress_tblspace; -- ok
+CLUSTER pg_authid USING pg_authid_rolname_index TABLESPACE regress_tblspace; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx TABLESPACE pg_global; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE) TABLESPACE pg_default regress_tblspace_test_tbl; -- ok
+VACUUM (FULL) TABLESPACE pg_default pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE) TABLESPACE pg_default; -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL) TABLESPACE pg_global regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--uc35eWnScqDcQrv5
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v15-0003-fixes2.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v27 3/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 66 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 55 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 +++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 226 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index e6ebce27e6..b198a7605c 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -95,6 +96,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -126,6 +136,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9419cc4dcf..3990dbaca5 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -69,7 +71,8 @@ typedef struct
static void rebuild_relation(Relation OldHeap, Oid indexOid,
- bool isTopLevel, bool verbose);
+ bool isTopLevel, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool isTopLevel, bool verbose,
bool *pSwapToastByContent,
@@ -107,6 +110,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
int options = 0;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -118,6 +124,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
options |= CLUOPT_VERBOSE;
else
options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -126,6 +134,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -195,7 +216,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, options, isTopLevel);
+ cluster_rel(tableOid, indexOid, options, isTopLevel,
+ tablespaceOid);
}
else
{
@@ -245,7 +267,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* Do the job. */
cluster_rel(rvtc->tableOid, rvtc->indexOid,
options | CLUOPT_RECHECK,
- isTopLevel);
+ isTopLevel, tablespaceOid);
PopActiveSnapshot();
CommitTransactionCommand();
}
@@ -274,9 +296,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
+cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel, Oid tablespaceOid)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -376,6 +401,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -426,7 +468,8 @@ cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, isTopLevel, verbose);
+ rebuild_relation(OldHeap, indexOid, isTopLevel, verbose,
+ tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -576,7 +619,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -587,6 +630,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1032,6 +1079,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 708f0d55cd..308a4ae7ec 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13122,8 +13122,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ddeec870d8..25e437056c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1724,8 +1754,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1861,6 +1892,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1930,7 +1978,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options, true);
+ cluster_rel(relid, InvalidOid, cluster_options, true,
+ tablespaceOid);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0256da5530..87b2042d4c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10438,8 +10438,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1b8cd7bacd..e231ce81ec 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2900,6 +2900,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5d2cd88de9..7c5ba238be 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2284,7 +2284,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3711,9 +3713,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index c761b9575a..9772aa1761 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -21,7 +21,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
- bool isTopLevel);
+ bool isTopLevel, Oid tablespaceOid);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index d9475c9989..b6e944d3a5 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 5d8a22cffb..f4687f6bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1169f0318b..e0a5c48fe9 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--fdj2RfSjLxBAspz7
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v27-0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++---
doc/src/sgml/ref/vacuum.sgml | 20 ++++++++
src/backend/commands/cluster.c | 57 +++++++++++++++++++++--
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 1 +
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++++-
12 files changed, 212 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 5dd21a0189..6758ef97a6 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -28,6 +28,7 @@ CLUSTER [VERBOSE]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -105,6 +106,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
@@ -115,15 +125,10 @@ CLUSTER [VERBOSE]
</varlistentry>
<varlistentry>
- <term><replaceable class="parameter">boolean</replaceable></term>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
<listitem>
<para>
- Specifies whether the selected option should be turned on or off.
- You can write <literal>TRUE</literal>, <literal>ON</literal>, or
- <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
- <literal>OFF</literal>, or <literal>0</literal> to disable it. The
- <replaceable class="parameter">boolean</replaceable> value can also
- be omitted, in which case <literal>TRUE</literal> is assumed.
+ The tablespace where the table will be rebuilt.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 21ab57d880..5261a7c727 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f7b3..626321e3ac 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,8 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose,
+ Oid NewTableSpaceOid);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -105,6 +108,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell *lc;
ClusterParams params = {0};
bool verbose = false;
+ /* Name of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
/* Parse option list */
foreach(lc, stmt->params)
@@ -113,6 +118,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
verbose = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -123,6 +130,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
params.options = (verbose ? CLUOPT_VERBOSE : 0);
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ params.tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (params.tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -272,6 +292,9 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
@@ -374,6 +397,23 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(params->tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(params->tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -424,7 +464,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, verbose, params->tablespaceOid);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -573,7 +613,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid NewTablespaceOid)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -584,6 +624,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1028,6 +1072,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..75b04eb161 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13196,8 +13196,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0f82..61565c3f14 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("TABLESPACE can only be used with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1718,8 +1748,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid;
int save_sec_context;
int save_nestlevel;
@@ -1857,6 +1887,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We don't support moving system relations into different tablespaces
+ * unless allow_system_table_mods=1.
+ */
+ if ((params->options & VACOPT_FULL) != 0 &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ params->tablespace_oid = InvalidOid;
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1916,7 +1962,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
*/
if (params->options & VACOPT_FULL)
{
- ClusterParams cluster_params = {0};
+ ClusterParams cluster_params = {
+ .tablespaceOid = params->tablespace_oid,
+ };
/* close relation before vacuuming, but hold lock until commit */
relation_close(onerel, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..24755a38d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10493,8 +10493,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca561..a5d98c8c95 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2932,6 +2932,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ac4b40561b..64a435865e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2321,7 +2321,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE", "VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3870,9 +3872,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index a941f2accd..868dea5ecc 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -27,6 +27,7 @@
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
+ Oid tablespaceOid; /* tablespace to rebuild relation, or InvalidOid */
} ClusterParams;
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 191cbbd004..0934487d4b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -227,6 +227,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 6e94ffe617..2244c1d51d 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 0927d1c7ad..889933a9c8 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: TABLESPACE can only be used with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--tsOsTdHNUZQcU9Ye
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0004-Implement-vacuum-full-cluster-INDEX_TABLESPACE-table.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v17 5/8] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 26 +++++++++-
doc/src/sgml/ref/vacuum.sgml | 21 ++++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 2 +
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 228 insertions(+), 17 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index bd0682ddfd..0e81e6189b 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -21,8 +21,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CLUSTER [VERBOSE] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ]
+CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ]
CLUSTER [VERBOSE]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ VERBOSE
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
+
</synopsis>
</refsynopsisdiv>
@@ -90,6 +96,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -108,6 +123,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 846056a353..b44b10c783 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -23,6 +23,7 @@ PostgreSQL documentation
<synopsis>
VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
+VACUUM ( FULL [, ...] ) [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
@@ -35,6 +36,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +257,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +310,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 0f25576a32..94a28f3cb4 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameter not handled by the parser */
foreach(lc, stmt->params)
@@ -112,6 +117,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
stmt->options |= CLUOPT_VERBOSE;
// XXX: handle boolean opt: VERBOSE off
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -120,6 +127,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -201,7 +221,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -249,7 +269,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -279,9 +299,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace to use for the rebuilt relation. If
+ * InvalidOid, use the tablespace in-use instead.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -394,6 +417,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -444,7 +484,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -603,7 +643,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -614,6 +654,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1058,6 +1102,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5f6a9fe3e2..103e299832 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12912,8 +12912,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 3a89f8fe1e..e8a2b10497 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -107,6 +110,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool parallel_option = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -143,6 +150,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
parallel_option = true;
@@ -204,6 +213,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot specify both FULL and PARALLEL options")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1672,8 +1702,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1807,6 +1838,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1876,7 +1924,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f6974f2cdb..ff59856465 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10685,6 +10685,8 @@ cluster_index_specification:
*
* QUERY:
* VACUUM
+ * VACUUM FULL [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
+ * VACUUM (FULL) [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
* ANALYZE
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffab27..8acc321faa 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2897,6 +2897,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3054a14ed5..2c34533744 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2275,7 +2275,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3689,9 +3691,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 2779bea5c9..6758e9812f 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace Oid to use for relations
+ * after VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 57715b3cca..6942775bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -41,11 +41,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 7733254416..2825984394 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -50,9 +50,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--byLs0wutDcxFdwtm
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0006-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v21 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4bdefcb4b7..b01e1aa8bf 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12963,8 +12963,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5a110edb07..e9373e5dae 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6fb4d814cc..93b35378d0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10676,8 +10676,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffab27..8acc321faa 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2897,6 +2897,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 630fae52df..48018507dc 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2279,7 +2279,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3691,9 +3693,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--SNIs70sCzqvszXB4
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v21-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 19 +++++++
doc/src/sgml/ref/vacuum.sgml | 20 +++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 5 +-
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 222 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index a6df8a3d81..a824694e68 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -27,6 +27,7 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -96,6 +97,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the table will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -128,6 +138,15 @@ CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index a48f75ad7b..2408b59bb2 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -35,6 +35,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +256,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +309,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 90a7dacbe7..dcb8f83d95 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameters not handled by the parser */
foreach(lc, stmt->params)
@@ -114,6 +119,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt->options |= CLUOPT_VERBOSE;
else
stmt->options &= ~CLUOPT_VERBOSE;
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -122,6 +129,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -191,7 +211,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -239,7 +259,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -269,9 +289,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace where the relation will be rebuilt, or
+ * InvalidOid to use its current tablespace.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -371,6 +394,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -421,7 +461,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -570,7 +610,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -581,6 +621,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1025,6 +1069,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02779a05d6..8f8b3cd93a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13008,8 +13008,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e63e9..9ea0328c95 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -106,6 +109,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -142,6 +149,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
if (opt->arg == NULL)
@@ -202,6 +211,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1670,8 +1700,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1805,6 +1836,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1874,7 +1922,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac489c03c3..b18e106303 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10443,8 +10443,9 @@ cluster_index_specification:
/*****************************************************************************
*
* QUERY:
- * VACUUM
- * ANALYZE
+ * VACUUM [FULL] [FREEZE] [VERBOSE] [ANALYZE] [ <table_and_columns> [, ...] ]
+ * VACUUM [(options)] [ <table_and_columns> [, ...] ]
+ * ANALYZE [VERBOSE] [ <table_and_columns> [, ...] ]
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..7804c16b8c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2896,6 +2896,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0ae0bf2a4b..690ca5e4d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2285,7 +2285,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3712,9 +3714,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a4cd721400..4b5ac7145d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace to use for relations
+ * rebuilt by VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 230cf46833..eb9dfcc0f4 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -47,11 +47,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ec5742df98..789a0e56cc 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -61,9 +61,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--g7w8+K/95kPelPD2
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* [PATCH v18 5/6] Allow CLUSTER and VACUUM FULL to change tablespace
@ 2020-03-24 15:16 Alexey Kondratov <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Alexey Kondratov @ 2020-03-24 15:16 UTC (permalink / raw)
---
doc/src/sgml/ref/cluster.sgml | 26 +++++++++-
doc/src/sgml/ref/vacuum.sgml | 21 ++++++++
src/backend/commands/cluster.c | 63 ++++++++++++++++++++---
src/backend/commands/tablecmds.c | 5 +-
src/backend/commands/vacuum.c | 54 +++++++++++++++++--
src/backend/parser/gram.y | 2 +
src/backend/postmaster/autovacuum.c | 1 +
src/bin/psql/tab-complete.c | 9 +++-
src/include/commands/cluster.h | 2 +-
src/include/commands/vacuum.h | 2 +
src/test/regress/input/tablespace.source | 23 ++++++++-
src/test/regress/output/tablespace.source | 37 ++++++++++++-
12 files changed, 228 insertions(+), 17 deletions(-)
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index bd0682ddfd..0e81e6189b 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -21,8 +21,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CLUSTER [VERBOSE] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ]
+CLUSTER [VERBOSE] [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">index_name</replaceable> ]
CLUSTER [VERBOSE]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ VERBOSE
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
+
</synopsis>
</refsynopsisdiv>
@@ -90,6 +96,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
@@ -108,6 +123,15 @@ CLUSTER [VERBOSE]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the relation will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 846056a353..b44b10c783 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -23,6 +23,7 @@ PostgreSQL documentation
<synopsis>
VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
+VACUUM ( FULL [, ...] ) [ <replaceable class="parameter">table_and_columns</replaceable> [, ...] ]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
@@ -35,6 +36,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable>
+ TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -255,6 +257,15 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>TABLESPACE</literal></term>
+ <listitem>
+ <para>
+ Specifies that the relation will be rebuilt on a new tablespace.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
@@ -299,6 +310,16 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_tablespace</replaceable></term>
+ <listitem>
+ <para>
+ The tablespace where the table will be rebuilt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 20c5a46791..0d631efb30 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,11 +33,13 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
@@ -68,7 +70,7 @@ typedef struct
} RelToCluster;
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTableSpaceOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
@@ -103,6 +105,9 @@ void
cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
{
ListCell *lc;
+ /* Name and Oid of tablespace to use for clustered relation. */
+ char *tablespaceName = NULL;
+ Oid tablespaceOid = InvalidOid;
/* Parse list of generic parameter not handled by the parser */
foreach(lc, stmt->params)
@@ -112,6 +117,8 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if (strcmp(opt->defname, "verbose") == 0)
stmt->options |= CLUOPT_VERBOSE;
// XXX: handle boolean opt: VERBOSE off
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespaceName = defGetString(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -120,6 +127,19 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location)));
}
+ /* Select tablespace Oid to use. */
+ if (tablespaceName)
+ {
+ tablespaceOid = get_tablespace_oid(tablespaceName, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespaceName)));
+ }
+
if (stmt->relation != NULL)
{
/* This is the single-relation case. */
@@ -189,7 +209,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
- cluster_rel(tableOid, indexOid, stmt->options);
+ cluster_rel(tableOid, indexOid, tablespaceOid, stmt->options);
}
else
{
@@ -237,7 +257,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
- cluster_rel(rvtc->tableOid, rvtc->indexOid,
+ cluster_rel(rvtc->tableOid, rvtc->indexOid, tablespaceOid,
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
@@ -267,9 +287,12 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order. This is the new implementation of VACUUM FULL,
* and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * "tablespaceOid" is the tablespace to use for the rebuilt relation. If
+ * InvalidOid, use the tablespace in-use instead.
*/
void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -369,6 +392,23 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster a shared catalog")));
+ if (OidIsValid(tablespaceOid) &&
+ !allowSystemTableMods && IsSystemRelation(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(OldHeap))));
+
+ /*
+ * We cannot support moving mapped relations into different tablespaces.
+ * (In particular this eliminates all shared catalogs.)
+ */
+ if (OidIsValid(tablespaceOid) && RelationIsMapped(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -419,7 +459,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, indexOid, verbose);
+ rebuild_relation(OldHeap, indexOid, tablespaceOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@@ -568,7 +608,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, Oid NewTablespaceOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@@ -579,6 +619,10 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ /* Use new tablespace if passed. */
+ if (OidIsValid(NewTablespaceOid))
+ tableSpace = NewTablespaceOid;
+
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
mark_index_clustered(OldHeap, indexOid, true);
@@ -1023,6 +1067,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
*/
Assert(!target_is_pg_class);
+ if (!allowSystemTableMods && IsSystemClass(r1, relform1) &&
+ relform1->reltablespace != relform2->reltablespace)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ get_rel_name(r1))));
+
swaptemp = relform1->relfilenode;
relform1->relfilenode = relform2->relfilenode;
relform2->relfilenode = swaptemp;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b3410379ec..f8fcbc1143 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12962,8 +12962,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
if (RelationIsMapped(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move system relation \"%s\"",
- RelationGetRelationName(rel))));
+ errmsg("cannot change tablespace of mapped relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/* Can't move a non-shared relation into pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 3a89f8fe1e..e8a2b10497 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,13 +31,16 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_tablespace.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
@@ -107,6 +110,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool parallel_option = false;
ListCell *lc;
+ /* Name and Oid of tablespace to use for relations after VACUUM FULL. */
+ char *tablespacename = NULL;
+ Oid tablespaceOid = InvalidOid;
+
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
@@ -143,6 +150,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
+ else if (strcmp(opt->defname, "tablespace") == 0)
+ tablespacename = defGetString(opt);
else if (strcmp(opt->defname, "parallel") == 0)
{
parallel_option = true;
@@ -204,6 +213,27 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot specify both FULL and PARALLEL options")));
+ /* Get tablespace Oid to use. */
+ if (tablespacename)
+ {
+ if ((params.options & VACOPT_FULL) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("incompatible TABLESPACE option"),
+ errdetail("You can only use TABLESPACE with VACUUM FULL.")));
+
+ tablespaceOid = get_tablespace_oid(tablespacename, false);
+
+ /* Can't move a non-shared relation into pg_global */
+ if (tablespaceOid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move non-shared relation to tablespace \"%s\"",
+ tablespacename)));
+
+ }
+ params.tablespace_oid = tablespaceOid;
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -1672,8 +1702,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
- Oid toast_relid;
- Oid save_userid;
+ Oid toast_relid,
+ save_userid,
+ tablespaceOid = InvalidOid;
int save_sec_context;
int save_nestlevel;
@@ -1807,6 +1838,23 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return true;
}
+ /*
+ * We cannot support moving system relations into different tablespaces,
+ * unless allow_system_table_mods=1.
+ */
+ if (params->options & VACOPT_FULL &&
+ OidIsValid(params->tablespace_oid) &&
+ IsSystemRelation(onerel) && !allowSystemTableMods)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("skipping tablespace change of \"%s\"",
+ RelationGetRelationName(onerel)),
+ errdetail("Cannot move system relation, only VACUUM is performed.")));
+ }
+ else
+ tablespaceOid = params->tablespace_oid;
+
/*
* Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the
@@ -1876,7 +1924,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
- cluster_rel(relid, InvalidOid, cluster_options);
+ cluster_rel(relid, InvalidOid, tablespaceOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9e90ad0748..3665ee8700 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10689,6 +10689,8 @@ cluster_index_specification:
*
* QUERY:
* VACUUM
+ * VACUUM FULL [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
+ * VACUUM (FULL) [ TABLESPACE <tablespace_name> ] [ <table_and_columns> [, ...] ]
* ANALYZE
*
*****************************************************************************/
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffab27..8acc321faa 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2897,6 +2897,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+ tab->at_params.tablespace_oid = InvalidOid;
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b0750d0b9a..5c340bf94f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2275,7 +2275,9 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE");
+ COMPLETE_WITH("TABLESPACE|VERBOSE");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
/* COMMENT */
@@ -3687,9 +3689,12 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
- "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
+ "INDEX_CLEANUP", "TRUNCATE", "PARALLEL",
+ "TABLESPACE");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
COMPLETE_WITH("ON", "OFF");
+ else if (TailMatches("TABLESPACE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 674cdcd0cd..bc9f881d8c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -20,7 +20,7 @@
extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, Oid tablespaceOid, int options);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 2779bea5c9..6758e9812f 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -229,6 +229,8 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ Oid tablespace_oid; /* tablespace Oid to use for relations
+ * after VACUUM FULL */
} VacuumParams;
/* GUC parameters */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 57715b3cca..6942775bfb 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -17,7 +17,7 @@ ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- f
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -41,11 +41,32 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am; -- fail
REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+
-- move indexes back to pg_default tablespace
REINDEX (TABLESPACE pg_default) TABLE CONCURRENTLY regress_tblspace_test_tbl; -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 7733254416..2825984394 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -20,7 +20,7 @@ ERROR: unrecognized parameter "some_nonexistent_parameter"
ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
ERROR: RESET must not include values for parameters
ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
--- create table to test REINDEX with TABLESPACE change
+-- create table to test REINDEX, CLUSTER and VACUUM FULL with TABLESPACE change
CREATE TABLE regress_tblspace_test_tbl (num1 bigint, num2 double precision, num3 double precision);
INSERT INTO regress_tblspace_test_tbl (num1, num2, num3)
SELECT round(random()*100), random(), random()*42
@@ -50,9 +50,44 @@ REINDEX (TABLESPACE pg_global) INDEX regress_tblspace_test_tbl_idx; -- fail
ERROR: cannot move non-shared relation to tablespace "pg_global"
REINDEX (TABLESPACE regress_tblspace) TABLE pg_am; -- fail
ERROR: permission denied: "pg_am_name_index" is a system catalog
+-- check that all indexes moved to new tablespace
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl_idx
+(1 row)
+
+-- check CLUSTER with TABLESPACE change
+CLUSTER (TABLESPACE regress_tblspace) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- ok
+CLUSTER (TABLESPACE regress_tblspace) pg_authid USING pg_authid_rolname_index; -- fail
+ERROR: cannot cluster a shared catalog
+CLUSTER (TABLESPACE pg_global) regress_tblspace_test_tbl USING regress_tblspace_test_tbl_idx; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
-- check that all relations moved to new tablespace
SELECT relname FROM pg_class
WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
+ORDER BY relname;
+ relname
+-------------------------------
+ regress_tblspace_test_tbl
+ regress_tblspace_test_tbl_idx
+(2 rows)
+
+-- check VACUUM with TABLESPACE change
+VACUUM (FULL, ANALYSE, FREEZE, TABLESPACE pg_default) regress_tblspace_test_tbl; -- ok
+VACUUM (FULL, TABLESPACE pg_default) pg_authid; -- skip with warning
+WARNING: skipping tablespace change of "pg_authid"
+DETAIL: Cannot move system relation, only VACUUM is performed.
+VACUUM (ANALYSE, TABLESPACE pg_default); -- fail
+ERROR: incompatible TABLESPACE option
+DETAIL: You can only use TABLESPACE with VACUUM FULL.
+VACUUM (FULL, TABLESPACE pg_global) regress_tblspace_test_tbl; -- fail
+ERROR: cannot move non-shared relation to tablespace "pg_global"
+-- check that all tables moved back to pg_default
+SELECT relname FROM pg_class
+WHERE reltablespace=(SELECT oid FROM pg_tablespace WHERE spcname='regress_tblspace')
ORDER BY relname;
relname
-------------------------------
--
2.17.0
--UPT3ojh+0CqEDtpF
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v18-0006-Implement-vacuum-full-cluster-INDEX_TABLESPACE-t.patch"
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
@ 2026-05-25 07:37 ` Peter Smith <[email protected]>
1 sibling, 0 replies; 99+ messages in thread
From: Peter Smith @ 2026-05-25 07:37 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Shlok -
I wonder if it might be worth reconsidering the split between patches
0001 and 0002. My understanding is that the only reason for splitting
is to make review easier, but it had the opposite effect for me.
The difficulty is that 0001 contains renaming of functions and
variables whose purpose only becomes clear in 0002. Without any
context, the changes in 0001 are hard to evaluate on their own merits
— anybody reading patch 0001 is left guessing what is coming rather
than seeing the full picture.
It also won't be saving much overall patch size, since AFAICT most of
the same locations are touched again in 0002 anyway.
There is also the matter of identifier renames (patch 0001) being
separated from their associated comment updates (0002). Keeping those
together would reduce the risk of anything being inadvertently missed.
I think a combined 0001/0002 patch would be easier to follow, since
reviewers could see each change alongside its reason.
======
Kind Regards
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
@ 2026-05-25 07:37 ` Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
1 sibling, 1 reply; 99+ messages in thread
From: Peter Smith @ 2026-05-25 07:37 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Shlok -
Here are some minor review comments for patch v5-0002.
======
doc/src/sgml/catalogs.sgml
1.
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or the sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
/or the sequence/or sequence/
======
src/backend/catalog/pg_publication.c
check_publication_add_relation:
2.
* Check if relation can be in given publication and throws appropriate
* error if not.
~
Too terse. There are missing words.
SUGGESTION
Check if the target relation is allowed to be specified in the given
publication and throw an error if not.
~~~
3.
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
if (pubrelkind == RELKIND_SEQUENCE)
errormsg = gettext_noop("cannot specify \"%s\" in the publication
EXCEPT (SEQUENCE) clause");
else
errormsg = gettext_noop("cannot specify \"%s\" in the publication
EXCEPT (TABLE) clause");
}
else
{
relname = RelationGetRelationName(targetrel);
errormsg = gettext_noop("cannot add relation \"%s\" to publication");
}
~
Wondering why sometimes the error `relname` is fully-qualified and
sometimes it is not. Isn't it better to always be qualified?
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-05-26 06:00 ` Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Shlok Kyal @ 2026-05-26 06:00 UTC (permalink / raw)
To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, 25 May 2026 at 13:08, Peter Smith <[email protected]> wrote:
>
> Hi Shlok -
>
> Here are some minor review comments for patch v5-0002.
>
> ======
> doc/src/sgml/catalogs.sgml
>
> 1.
> <para>
> - True if the table is excluded from the publication. See
> - <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
> + True if the table or the sequence is excluded from the publication. See
> + <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
>
> /or the sequence/or sequence/
>
> ======
> src/backend/catalog/pg_publication.c
>
> check_publication_add_relation:
>
> 2.
> * Check if relation can be in given publication and throws appropriate
> * error if not.
>
> ~
>
> Too terse. There are missing words.
>
> SUGGESTION
> Check if the target relation is allowed to be specified in the given
> publication and throw an error if not.
>
> ~~~
>
> 3.
> if (pri->except)
> {
> relname = RelationGetQualifiedRelationName(targetrel);
> if (pubrelkind == RELKIND_SEQUENCE)
> errormsg = gettext_noop("cannot specify \"%s\" in the publication
> EXCEPT (SEQUENCE) clause");
> else
> errormsg = gettext_noop("cannot specify \"%s\" in the publication
> EXCEPT (TABLE) clause");
> }
> else
> {
> relname = RelationGetRelationName(targetrel);
> errormsg = gettext_noop("cannot add relation \"%s\" to publication");
> }
>
> ~
>
> Wondering why sometimes the error `relname` is fully-qualified and
> sometimes it is not. Isn't it better to always be qualified?
>
This change was made as part of thread [1]. And by reading the thread
I understood that we made 'relname' fully-qualified for the EXCEPT
case.
For the non-EXCEPT case the patch is still in discussion.
I think making `relname` fully-qualified for non-EXCEPT is not related
to this patch and should be discussed in thread [1].
I have addressed the remaining comments in the v6 patches. I have also
merged the patches 0001 and 0002.
[1]: https://www.postgresql.org/message-id/CAA4eK1LvV6ex8n1UV_HZ%2Bs77y%2B5wOpbCns-0rF95Gu3EF0SPNA%40mail...
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v6-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLIC.patch (58.7K, ../../CANhcyEU+4Z6NXMEk6OmctFv=_pY8K5AiazkfVmn7zxhwQO6CEQ@mail.gmail.com/2-v6-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLIC.patch)
download | inline diff:
From 1d2f6b756e5f02e3eee3a9741f2ac90844b9be1a Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v6 1/2] Support EXCEPT for ALL SEQUENCES in CREATE PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 6 +-
doc/src/sgml/ref/create_publication.sgml | 37 ++++++--
src/backend/catalog/pg_publication.c | 68 ++++++++-----
src/backend/commands/publicationcmds.c | 109 ++++++++++++---------
src/backend/parser/gram.y | 111 ++++++++++++++--------
src/bin/pg_dump/pg_dump.c | 56 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 +++++
src/bin/psql/describe.c | 73 ++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 11 ++-
src/test/regress/expected/publication.out | 59 +++++++++++-
src/test/regress/sql/publication.sql | 28 +++++-
src/test/subscription/t/037_except.pl | 75 +++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 533 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 9e7868487de..eb0b66491d3 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -119,7 +119,11 @@
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index f82d640e6ca..df736e74d2f 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,15 +193,18 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
@@ -217,9 +224,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -547,11 +555,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..5fd2c2795ab 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause,
+ * while 'targetrelkind' is the relkind of the relation being added.
+ * Error if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,14 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+ Assert(GetPublication(pubid)->alltables ||
+ GetPublication(pubid)->allsequences);
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1060,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * mentioned in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences mentioned in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1076,10 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ /* EXCEPT filtering applies to tables and sequences */
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..e97578ddd75 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -177,11 +177,12 @@ parse_publication_options(ParseState *pstate,
/*
* Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * PublicationRelation list.
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,27 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* Process EXCEPT sequence list */
+ if (stmt->for_all_sequences && exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
List *rels;
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ rels = OpenRelationList(excepttbls);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +984,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +992,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1270,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1281,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1304,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1378,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1425,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1666,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1698,16 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* EXCEPT clause is not supported with ALTER PUBLICATION */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1730,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1849,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1867,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2006,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2051,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2069,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..c7a499ab96d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d56dcc701ce..ee2331f6345 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,21 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4733,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+
+ if (++n_except == 1)
+ appendPQExpBufferStr(query, " EXCEPT (");
+ else
+ appendPQExpBufferStr(query, ", ");
+ appendPQExpBuffer(query, "SEQUENCE %s", fmtQualifiedDumpable(tbinfo));
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 3ee9fda50e4..4b2c88e23a2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3242,6 +3242,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4042,6 +4062,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e1449654f96..1f67c310177 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,11 +1882,16 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
"\n AND pg_catalog.pg_relation_is_publishable('%s')"
+ "\n AND NOT EXISTS (\n"
+ " SELECT 1\n"
+ " FROM pg_catalog.pg_publication_rel pr\n"
+ " WHERE pr.prpubid = p.oid AND\n"
+ " pr.prrelid = '%s')"
"\nORDER BY 1",
- oid);
+ oid, oid);
result = PSQLexec(buf.data);
if (result)
@@ -1912,6 +1917,42 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -6950,6 +6991,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7028,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7082,7 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,13 +7094,32 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
goto error_return;
}
}
+ if (puballsequences)
+ {
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index de547a8cb37..12d276cbb65 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3761,6 +3761,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 91377a6cde3..204aea610cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4465,14 +4465,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication relation */
Node *whereClause; /* qualifications */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4481,6 +4481,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4492,7 +4493,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4509,7 +4510,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..15c30ffeefc 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -473,6 +473,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -534,10 +535,64 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "public.regress_pub_seq0"
+ "public.regress_pub_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+ Sequence "public.regress_pub_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences3"
+
+RESET client_min_messages;
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+ Publication regress_pub_for_allsequences_alltables1
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.testpub_tbl1"
+Except sequences:
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..d472553c7cd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -223,6 +223,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,35 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+RESET client_min_messages;
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+RESET client_min_messages;
+
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..c03c77bb41e 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,81 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '100|t', 'initial test data replicated for seq2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence list
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub1, tap_pub2;
+ ALTER SUBSCRIPTION tap_sub REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq1 is excluded in tap_pub1 but included in tap_pub2, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq2 is excluded in tap_pub2 but included in tap_pub1, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+$node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8cf40c87043..a04d7de3cdc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2473,8 +2473,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
[application/octet-stream] v6-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLICA.patch (24.1K, ../../CANhcyEU+4Z6NXMEk6OmctFv=_pY8K5AiazkfVmn7zxhwQO6CEQ@mail.gmail.com/3-v6-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLICA.patch)
download | inline diff:
From bb6293e6ab0831e8f9cebf9c6987b591da862978 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Mon, 11 May 2026 10:59:28 +0530
Subject: [PATCH v6 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 22 +-
src/backend/catalog/pg_publication.c | 23 +-
src/backend/commands/publicationcmds.c | 253 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 3 +-
src/test/regress/expected/publication.out | 16 ++
src/test/regress/sql/publication.sql | 5 +
7 files changed, 201 insertions(+), 135 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index aa32bb169e9..3ce1d5d1539 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -75,7 +79,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
The third variant either modifies the included tables/schemas
or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
<literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5fd2c2795ab..045b16ecf21 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -973,8 +973,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION && relkind == RELKIND_RELATION) ||
+ (pubrelkind == RELKIND_RELATION && relkind == RELKIND_PARTITIONED_TABLE) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -998,7 +1005,7 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,12 +1013,13 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
Assert(GetPublication(pubid)->alltables ||
GetPublication(pubid)->allsequences);
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1079,7 +1087,8 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
/* EXCEPT filtering applies to tables and sequences */
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ relkind);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index e97578ddd75..3bfc94ebeae 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1251,14 +1251,118 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Recreate list of tables/sequences to be dropped from the publication.
+ * To recreate the relation list for the publication, look for existing
+ * relations that do not need to be dropped.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+get_delete_rels(Oid pubid, List *rels, List *oldrelids, List **delrels)
+{
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ /*
+ * Check if any of the new set of relations matches with the
+ * existing relations in the publication. Additionally, if the
+ * relation has an associated WHERE clause, check the WHERE
+ * expressions also match. Same for the column list. Drop the
+ * rest.
+ */
+ if (newrelid == oldrelid)
+ {
+ if (equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns))
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Add the non-matched relations to a list so that they can be
+ * dropped.
+ */
+ if (!found)
+ {
+ oldrel = palloc_object(PublicationRelInfo);
+ oldrel->whereClause = NULL;
+ oldrel->columns = NIL;
+ oldrel->except = false;
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ *delrels = lappend(*delrels, oldrel);
+ }
+ }
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
+ */
+static void
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
@@ -1271,6 +1375,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1284,29 +1389,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ SEQUENCES mode, relations are tracked as
+ * exclusions (EXCEPT clause). Fetch the current excluded
+ * relations so they can be reconciled with the specified EXCEPT
+ * list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1319,117 +1428,24 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ get_delete_rels(pubid, rels, oldrelids, &delrels);
+ get_delete_rels(pubid, seqs, oldseqids, &delrels);
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
+ CloseRelationList(seqs);
CloseRelationList(rels);
}
@@ -1706,9 +1722,6 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /* EXCEPT clause is not supported with ALTER PUBLICATION */
- Assert(exceptseqs == NIL);
-
CheckAlterPublication(stmt, tup, relations, schemaidlist);
heap_freetuple(tup);
@@ -1731,8 +1744,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2090,7 +2103,7 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
* Remove listed tables from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 12d276cbb65..aaf03e3a94a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..e8e6baa6c40 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,7 +178,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 15c30ffeefc..26b2f57fd4f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -562,16 +562,32 @@ Included in publications:
Excluded from publications:
"regress_pub_forallsequences3"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_pub_seq0"
+
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d472553c7cd..998ae3e4e4f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -261,14 +261,19 @@ CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUEN
-- Check that the sequence description shows the publications where it is listed
-- in the EXCEPT clause
\d+ regress_pub_seq0
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-05-27 03:37 ` Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Peter Smith @ 2026-05-27 03:37 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Shlok,
No more comments for v6-0001.
Below are some review comments for v6-0002.
======
doc/src/sgml/ref/alter_publication.sgml
1.
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
TBH, I think this part of the description is repetitive and
unnecessarily difficult to read. OTOH, fixing it is probably outside
the scope of this patch. Perhaps we can revisit and address all this
properly after both your EXCEPT changes and Nisha's EXCEPT [1] changes
get combined?
~
Examples:
2.
There are many other small examples, so should you also add one also
for this syntax?
======
src/backend/catalog/pg_publication.c
3.
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
IIUC there should be a sanity Assert in this function to check that
`pubrelkind` can only be either RELKIND_RELATION or RELKIND_SEQUENCE.
~~~
GetIncludedPublicationRelations:
4.
/*
* Gets list of relation oids that are associated with a publication.
*
* This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
* should use GetAllPublicationRelations().
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
The comment does not match the code/assert. Shouldn't it also do
assert !allsequences?
~~~
GetAllPublicationRelations:
5.
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ relkind);
IIUC, that `relkind` should be renamed to pubrelkind.
======
src/backend/commands/publicationcmds.c
get_delete_rels:
6.
+get_delete_rels(Oid pubid, List *rels, List *oldrelids, List **delrels)
Add some function comments to describe these parameters.
I also wondered if it would be better to return `delrels` from this
function instead of void. Let the caller accumulate them.
~~~
7.
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ /*
+ * Check if any of the new set of relations matches with the
+ * existing relations in the publication. Additionally, if the
+ * relation has an associated WHERE clause, check the WHERE
+ * expressions also match. Same for the column list. Drop the
+ * rest.
+ */
IIUC, that comment "Check if any of the new..." is really describing
the purpose of this entire loop. IMO, this comment would be better put
atop the "foreach(newlc, rels)".
~
8.
+ if (newrelid == oldrelid)
+ {
+ if (equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns))
+ {
+ found = true;
+ break;
+ }
+ }
+ }
Consider writing that in a simpler way.
SUGGESTION
found = (newrelid == oldrelid) &&
equal(oldrelwhereclause, newpubrel->whereClause) &&
bms_equal(oldcolumns, newcolumns);
if (found)
break;
~~~
9.
+ /*
+ * Add the non-matched relations to a list so that they can be
+ * dropped.
+ */
+ if (!found)
+ {
+ oldrel = palloc_object(PublicationRelInfo);
+ oldrel->whereClause = NULL;
+ oldrel->columns = NIL;
+ oldrel->except = false;
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ *delrels = lappend(*delrels, oldrel);
+ }
9a.
There seems to be some implicit knowledge that the later DROP is only
going to care about the relation and the other whereClause/columns/etc
will have nothing to do with the dropping. Maybe the comment needs to
say something abouit that?
~
9b.
Maybe palloc0_object can be used instead of explicitly setting
everything else you don't care about to NULL/NIL/false?
~~~
AlterPublicationRelations:
10.
/*
* Nothing to do if no objects, except in SET: for that it is quite
* possible that user has not specified any tables in which case we need
* to remove all the existing tables.
*/
if (!tables && stmt->action != AP_SetObjects)
return;
~
The above code comment seems stale because it is not saying anything
about sequences, and I think it ought to be.
~~~
11.
+ CloseRelationList(seqs);
CloseRelationList(rels);
Everywhere else rels came before seqs. So maybe reverse these to match.
~~~
PublicationDropRelations:
12.
* Remove listed tables from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
The function comment should mention sequences as well.
======
[1] https://www.postgresql.org/message-id/flat/CAHut%2BPvBjw8JJOksjJsCN%2BU4Lda0vWAQTYaYy7ucuMMr8stj0w%4...
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-05-27 13:03 ` Shlok Kyal <[email protected]>
2026-05-27 21:51 ` Re: Support EXCEPT for ALL SEQUENCES publications Zsolt Parragi <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
0 siblings, 2 replies; 99+ messages in thread
From: Shlok Kyal @ 2026-05-27 13:03 UTC (permalink / raw)
To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, 27 May 2026 at 09:08, Peter Smith <[email protected]> wrote:
>
> Hi Shlok,
>
> No more comments for v6-0001.
>
> Below are some review comments for v6-0002.
>
> ======
> doc/src/sgml/ref/alter_publication.sgml
>
> 1.
> <literal>SET ALL TABLES</literal> can be used to update the tables specified
> in the <literal>EXCEPT</literal> clause of a
> - <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
> - is specified with a list of tables, the existing exclusion list is replaced
> - with the specified tables. If <literal>EXCEPT</literal> is omitted, the
> - existing exclusion list is cleared. The <literal>SET</literal> clause, when
> - used with a publication defined with <literal>FOR TABLE</literal> or
> + <literal>FOR ALL TABLES</literal> publication and
> + <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
> + specified in the <literal>EXCEPT</literal> clause of a
> + <literal>FOR ALL SEQUENCES</literal> publication. If
> + <literal>EXCEPT</literal> is specified with a list of tables or sequences,
> + the existing exclusion list is replaced with the specified tables or
> + sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
> + list is cleared. The <literal>SET</literal> clause, when used with a
> + publication defined with <literal>FOR TABLE</literal> or
> <literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
> in the publication with the specified list; the existing tables or schemas
> that were present in the publication will be removed.
>
> TBH, I think this part of the description is repetitive and
> unnecessarily difficult to read. OTOH, fixing it is probably outside
> the scope of this patch. Perhaps we can revisit and address all this
> properly after both your EXCEPT changes and Nisha's EXCEPT [1] changes
> get combined?
>
I agree.
> ~
>
> Examples:
>
> 2.
> There are many other small examples, so should you also add one also
> for this syntax?
>
> ======
> src/backend/catalog/pg_publication.c
>
> 3.
> static List *
> get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
> - bool except_flag)
> + bool except_flag, char pubrelkind)
>
> IIUC there should be a sanity Assert in this function to check that
> `pubrelkind` can only be either RELKIND_RELATION or RELKIND_SEQUENCE.
>
> ~~~
>
> GetIncludedPublicationRelations:
>
> 4.
> /*
> * Gets list of relation oids that are associated with a publication.
> *
> * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
> * should use GetAllPublicationRelations().
> */
> List *
> GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
> {
> Assert(!GetPublication(pubid)->alltables);
>
> return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
> }
>
> The comment does not match the code/assert. Shouldn't it also do
> assert !allsequences?
>
This function can be called for ALL SEQUENCES publication. For example
'pg_get_publication_tables', 'InvalidatePubRelSyncCache', etc can call
this function for all sequence publications, but in this case the list
returned is empty. So, there is no overall impact.
So, we should not add an 'assert !allsequences'.
This has been already discussed here [1].
> ~~~
>
> GetAllPublicationRelations:
>
> 5.
> exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
> PUBLICATION_PART_ROOT :
> - PUBLICATION_PART_LEAF);
> + PUBLICATION_PART_LEAF,
> + relkind);
>
> IIUC, that `relkind` should be renamed to pubrelkind.
>
> ======
> src/backend/commands/publicationcmds.c
>
> get_delete_rels:
>
> 6.
> +get_delete_rels(Oid pubid, List *rels, List *oldrelids, List **delrels)
>
> Add some function comments to describe these parameters.
>
> I also wondered if it would be better to return `delrels` from this
> function instead of void. Let the caller accumulate them.
>
> ~~~
>
> 7.
> + foreach(newlc, rels)
> + {
> + PublicationRelInfo *newpubrel;
> + Oid newrelid;
> + Bitmapset *newcolumns = NULL;
> +
> + newpubrel = (PublicationRelInfo *) lfirst(newlc);
> + newrelid = RelationGetRelid(newpubrel->relation);
> +
> + /*
> + * Validate the column list. If the column list or WHERE clause
> + * changes, then the validation done here will be duplicated
> + * inside PublicationAddRelations(). The validation is cheap
> + * enough that that seems harmless.
> + */
> + newcolumns = pub_collist_validate(newpubrel->relation,
> + newpubrel->columns);
> +
> + /*
> + * Check if any of the new set of relations matches with the
> + * existing relations in the publication. Additionally, if the
> + * relation has an associated WHERE clause, check the WHERE
> + * expressions also match. Same for the column list. Drop the
> + * rest.
> + */
>
> IIUC, that comment "Check if any of the new..." is really describing
> the purpose of this entire loop. IMO, this comment would be better put
> atop the "foreach(newlc, rels)".
>
> ~
>
> 8.
> + if (newrelid == oldrelid)
> + {
> + if (equal(oldrelwhereclause, newpubrel->whereClause) &&
> + bms_equal(oldcolumns, newcolumns))
> + {
> + found = true;
> + break;
> + }
> + }
> + }
>
> Consider writing that in a simpler way.
>
> SUGGESTION
> found = (newrelid == oldrelid) &&
> equal(oldrelwhereclause, newpubrel->whereClause) &&
> bms_equal(oldcolumns, newcolumns);
>
> if (found)
> break;
>
> ~~~
>
> 9.
> + /*
> + * Add the non-matched relations to a list so that they can be
> + * dropped.
> + */
> + if (!found)
> + {
> + oldrel = palloc_object(PublicationRelInfo);
> + oldrel->whereClause = NULL;
> + oldrel->columns = NIL;
> + oldrel->except = false;
> + oldrel->relation = table_open(oldrelid,
> + ShareUpdateExclusiveLock);
> + *delrels = lappend(*delrels, oldrel);
> + }
>
> 9a.
> There seems to be some implicit knowledge that the later DROP is only
> going to care about the relation and the other whereClause/columns/etc
> will have nothing to do with the dropping. Maybe the comment needs to
> say something abouit that?
>
> ~
>
> 9b.
> Maybe palloc0_object can be used instead of explicitly setting
> everything else you don't care about to NULL/NIL/false?
>
> ~~~
>
> AlterPublicationRelations:
>
> 10.
> /*
> * Nothing to do if no objects, except in SET: for that it is quite
> * possible that user has not specified any tables in which case we need
> * to remove all the existing tables.
> */
> if (!tables && stmt->action != AP_SetObjects)
> return;
>
> ~
>
> The above code comment seems stale because it is not saying anything
> about sequences, and I think it ought to be.
>
> ~~~
>
> 11.
> + CloseRelationList(seqs);
> CloseRelationList(rels);
>
> Everywhere else rels came before seqs. So maybe reverse these to match.
>
> ~~~
>
> PublicationDropRelations:
>
> 12.
> * Remove listed tables from the publication.
> */
> static void
> -PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
> +PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
>
> The function comment should mention sequences as well.
>
> ======
> [1] https://www.postgresql.org/message-id/flat/CAHut%2BPvBjw8JJOksjJsCN%2BU4Lda0vWAQTYaYy7ucuMMr8stj0w%4...
I have addressed the remaining comments as well and attached the v7 patch.
[1]: https://www.postgresql.org/message-id/[email protected]...
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v7-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLIC.patch (58.7K, ../../CANhcyEV_Sv+xQzsjo6hbowDbGV5J7RhFWuQQxWeUWPNPd0k1=w@mail.gmail.com/2-v7-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLIC.patch)
download | inline diff:
From b5936fe7153515bc4d9b90c3ddae67e7563268cd Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v7 1/2] Support EXCEPT for ALL SEQUENCES in CREATE PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 6 +-
doc/src/sgml/ref/create_publication.sgml | 37 ++++++--
src/backend/catalog/pg_publication.c | 68 ++++++++-----
src/backend/commands/publicationcmds.c | 109 ++++++++++++---------
src/backend/parser/gram.y | 111 ++++++++++++++--------
src/bin/pg_dump/pg_dump.c | 56 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 +++++
src/bin/psql/describe.c | 73 ++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 11 ++-
src/test/regress/expected/publication.out | 59 +++++++++++-
src/test/regress/sql/publication.sql | 28 +++++-
src/test/subscription/t/037_except.pl | 75 +++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 533 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 9e7868487de..eb0b66491d3 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -119,7 +119,11 @@
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index f82d640e6ca..df736e74d2f 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,15 +193,18 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
@@ -217,9 +224,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -547,11 +555,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..5fd2c2795ab 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause,
+ * while 'targetrelkind' is the relkind of the relation being added.
+ * Error if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,14 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+ Assert(GetPublication(pubid)->alltables ||
+ GetPublication(pubid)->allsequences);
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1060,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * mentioned in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences mentioned in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1076,10 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ /* EXCEPT filtering applies to tables and sequences */
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..e97578ddd75 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -177,11 +177,12 @@ parse_publication_options(ParseState *pstate,
/*
* Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * PublicationRelation list.
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,27 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* Process EXCEPT sequence list */
+ if (stmt->for_all_sequences && exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
List *rels;
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ rels = OpenRelationList(excepttbls);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +984,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +992,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1270,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1281,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1304,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1378,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1425,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1666,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1698,16 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* EXCEPT clause is not supported with ALTER PUBLICATION */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1730,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1849,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1867,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2006,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2051,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2069,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..c7a499ab96d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d56dcc701ce..ee2331f6345 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,21 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4733,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+
+ if (++n_except == 1)
+ appendPQExpBufferStr(query, " EXCEPT (");
+ else
+ appendPQExpBufferStr(query, ", ");
+ appendPQExpBuffer(query, "SEQUENCE %s", fmtQualifiedDumpable(tbinfo));
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 3ee9fda50e4..4b2c88e23a2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3242,6 +3242,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4042,6 +4062,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e1449654f96..1f67c310177 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,11 +1882,16 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
"\n AND pg_catalog.pg_relation_is_publishable('%s')"
+ "\n AND NOT EXISTS (\n"
+ " SELECT 1\n"
+ " FROM pg_catalog.pg_publication_rel pr\n"
+ " WHERE pr.prpubid = p.oid AND\n"
+ " pr.prrelid = '%s')"
"\nORDER BY 1",
- oid);
+ oid, oid);
result = PSQLexec(buf.data);
if (result)
@@ -1912,6 +1917,42 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -6950,6 +6991,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7028,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7082,7 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,13 +7094,32 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
goto error_return;
}
}
+ if (puballsequences)
+ {
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index de547a8cb37..12d276cbb65 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3761,6 +3761,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 91377a6cde3..204aea610cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4465,14 +4465,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication relation */
Node *whereClause; /* qualifications */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4481,6 +4481,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4492,7 +4493,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4509,7 +4510,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..15c30ffeefc 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -473,6 +473,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -534,10 +535,64 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "public.regress_pub_seq0"
+ "public.regress_pub_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+ Sequence "public.regress_pub_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences3"
+
+RESET client_min_messages;
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+ Publication regress_pub_for_allsequences_alltables1
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.testpub_tbl1"
+Except sequences:
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..d472553c7cd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -223,6 +223,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,35 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+RESET client_min_messages;
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+RESET client_min_messages;
+
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..c03c77bb41e 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,81 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '100|t', 'initial test data replicated for seq2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence list
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub1, tap_pub2;
+ ALTER SUBSCRIPTION tap_sub REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq1 is excluded in tap_pub1 but included in tap_pub2, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq2 is excluded in tap_pub2 but included in tap_pub1, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+$node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8cf40c87043..a04d7de3cdc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2473,8 +2473,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
[application/octet-stream] v7-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLICA.patch (26.7K, ../../CANhcyEV_Sv+xQzsjo6hbowDbGV5J7RhFWuQQxWeUWPNPd0k1=w@mail.gmail.com/3-v7-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLICA.patch)
download | inline diff:
From 24e3b8ee76503fb59dad223df4ece527f4db71c7 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Mon, 11 May 2026 10:59:28 +0530
Subject: [PATCH v7 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 41 +++-
src/backend/catalog/pg_publication.c | 31 ++-
src/backend/commands/publicationcmds.c | 261 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 16 ++
src/test/regress/sql/publication.sql | 5 +
7 files changed, 231 insertions(+), 143 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index aa32bb169e9..15b97efd566 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -75,7 +79,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
The third variant either modifies the included tables/schemas
or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
<literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -277,6 +285,25 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
tables:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's EXCEPT clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Reset the publication to be a ALL SEQUENCES publication with no excluded
+ sequences:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's EXCEPT clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5fd2c2795ab..686441fcab9 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -954,6 +954,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -973,8 +975,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION && relkind == RELKIND_RELATION) ||
+ (pubrelkind == RELKIND_RELATION && relkind == RELKIND_PARTITIONED_TABLE) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -998,7 +1007,7 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,12 +1015,13 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
Assert(GetPublication(pubid)->alltables ||
GetPublication(pubid)->allsequences);
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1065,7 +1075,7 @@ GetAllTablesPublications(void)
* it excludes sequences mentioned in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1074,19 +1084,20 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
/* EXCEPT filtering applies to tables and sequences */
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index e97578ddd75..6e1d1772e67 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1251,26 +1251,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Recreate list of tables/sequences to be dropped from the publication.
+ * To recreate the relation list for the publication, look for existing
+ * relations that do not need to be dropped.
+ *
+ * 'rels' contains the given list of relations, and 'oldrelids' contains
+ * the OIDs of existing relations in the publication identified by 'pubid'.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if(found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object (PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
* Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * possible that user has not specified any tables or sequences in which
+ * case we need to remove all the existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1284,29 +1389,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ SEQUENCES mode, relations are tracked as
+ * exclusions (EXCEPT clause). Fetch the current excluded
+ * relations so they can be reconciled with the specified EXCEPT
+ * list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1319,118 +1428,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1706,9 +1722,6 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /* EXCEPT clause is not supported with ALTER PUBLICATION */
- Assert(exceptseqs == NIL);
-
CheckAlterPublication(stmt, tup, relations, schemaidlist);
heap_freetuple(tup);
@@ -1731,8 +1744,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2087,10 +2100,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 12d276cbb65..aaf03e3a94a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 15c30ffeefc..26b2f57fd4f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -562,16 +562,32 @@ Included in publications:
Excluded from publications:
"regress_pub_forallsequences3"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_pub_seq0"
+
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d472553c7cd..998ae3e4e4f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -261,14 +261,19 @@ CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUEN
-- Check that the sequence description shows the publications where it is listed
-- in the EXCEPT clause
\d+ regress_pub_seq0
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-05-27 21:51 ` Zsolt Parragi <[email protected]>
2026-05-29 11:59 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
1 sibling, 1 reply; 99+ messages in thread
From: Zsolt Parragi @ 2026-05-27 21:51 UTC (permalink / raw)
To: [email protected]
Hello!
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
Shouldn't the matching free calls also updated, this now leaks footers[2]?
+ "\n AND NOT EXISTS (\n"
+ " SELECT 1\n"
+ " FROM pg_catalog.pg_publication_rel pr\n"
+ " WHERE pr.prpubid = p.oid AND\n"
+ " pr.prrelid = '%s')"
Isn't a pr.prexcept check missing from this? It is included in other queries.
- /* EXCEPT clause is not supported with ALTER PUBLICATION */
- Assert(exceptseqs == NIL);
-
Would it be worth it to keep a more restricted assert, saying that
EXCEPT clause is only supported for ALTER PUBLICATION ... SET?
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 21:51 ` Re: Support EXCEPT for ALL SEQUENCES publications Zsolt Parragi <[email protected]>
@ 2026-05-29 11:59 ` Shlok Kyal <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Shlok Kyal @ 2026-05-29 11:59 UTC (permalink / raw)
To: Zsolt Parragi <[email protected]>; +Cc: [email protected]
Hi Zsolt,
Thanks for reviewing the patch.
On Thu, 28 May 2026 at 03:22, Zsolt Parragi <[email protected]> wrote:
>
> Hello!
>
> + if (footers[0] == NULL)
> + footers[0] = pg_strdup(tmpbuf.data);
> + else if (footers[1] == NULL)
> + footers[1] = pg_strdup(tmpbuf.data);
> + else
> + footers[2] = pg_strdup(tmpbuf.data);
> + resetPQExpBuffer(&tmpbuf);
>
> Shouldn't the matching free calls also updated, this now leaks footers[2]?
>
Yes, correct. Made the change for the same.
> + "\n AND NOT EXISTS (\n"
> + " SELECT 1\n"
> + " FROM pg_catalog.pg_publication_rel pr\n"
> + " WHERE pr.prpubid = p.oid AND\n"
> + " pr.prrelid = '%s')"
>
> Isn't a pr.prexcept check missing from this? It is included in other queries.
>
pg_publication_rel has an entry for table/sequence if we specifically
include/exclude it in a publication.
So, we can say, pg_publication_rel contains an entry for a
Table/Sequence for a ALL TABLES/ ALL SEQUENCE publication, only if it
is specified in the EXCEPT clause.
So, the above query will give the expected results even if we donot
use the ' pr.prexcept' flag.
For the case: _("Get publications that publish this table"))
Here as well we use a similar query without prexcept:
"WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('%s')\n"
" AND NOT EXISTS (\n"
" SELECT 1\n"
" FROM
pg_catalog.pg_publication_rel pr\n"
" WHERE pr.prpubid = p.oid AND\n"
" (pr.prrelid = '%s' OR
pr.prrelid = pg_catalog.pg_partition_root('%s')))\n"
"ORDER BY 1;",
> - /* EXCEPT clause is not supported with ALTER PUBLICATION */
> - Assert(exceptseqs == NIL);
> -
>
> Would it be worth it to keep a more restricted assert, saying that
> EXCEPT clause is only supported for ALTER PUBLICATION ... SET?
>
>
Added the Assert in 0002 patch.
I have addressed the comments and attached the v8 patch.
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v8-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLIC.patch (58.9K, ../../CANhcyEVCJOZTH9GL2wCOA+ea5uM_yqemUu2vAG-chNuA29HuJA@mail.gmail.com/2-v8-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLIC.patch)
download | inline diff:
From ca6d8c951725e504fdda5fb91f9878a28de81765 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v8 1/2] Support EXCEPT for ALL SEQUENCES in CREATE PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 6 +-
doc/src/sgml/ref/create_publication.sgml | 37 ++++++--
src/backend/catalog/pg_publication.c | 68 ++++++++-----
src/backend/commands/publicationcmds.c | 109 ++++++++++++---------
src/backend/parser/gram.y | 111 ++++++++++++++--------
src/bin/pg_dump/pg_dump.c | 56 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 +++++
src/bin/psql/describe.c | 74 +++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 11 ++-
src/test/regress/expected/publication.out | 59 +++++++++++-
src/test/regress/sql/publication.sql | 28 +++++-
src/test/subscription/t/037_except.pl | 75 +++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 534 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 9e7868487de..eb0b66491d3 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -119,7 +119,11 @@
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index f82d640e6ca..df736e74d2f 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,15 +193,18 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
@@ -217,9 +224,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -547,11 +555,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..5fd2c2795ab 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause,
+ * while 'targetrelkind' is the relkind of the relation being added.
+ * Error if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,14 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+ Assert(GetPublication(pubid)->alltables ||
+ GetPublication(pubid)->allsequences);
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1060,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * mentioned in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences mentioned in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1076,10 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ /* EXCEPT filtering applies to tables and sequences */
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..e97578ddd75 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -177,11 +177,12 @@ parse_publication_options(ParseState *pstate,
/*
* Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * PublicationRelation list.
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,27 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* Process EXCEPT sequence list */
+ if (stmt->for_all_sequences && exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
List *rels;
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ rels = OpenRelationList(excepttbls);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +984,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +992,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1270,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1281,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1304,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1378,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1425,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1666,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1698,16 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* EXCEPT clause is not supported with ALTER PUBLICATION */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1730,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1849,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1867,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2006,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2051,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2069,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..c7a499ab96d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d56dcc701ce..ee2331f6345 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,21 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4733,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+
+ if (++n_except == 1)
+ appendPQExpBufferStr(query, " EXCEPT (");
+ else
+ appendPQExpBufferStr(query, ", ");
+ appendPQExpBuffer(query, "SEQUENCE %s", fmtQualifiedDumpable(tbinfo));
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 3ee9fda50e4..4b2c88e23a2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3242,6 +3242,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4042,6 +4062,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e1449654f96..e3e7186e011 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,11 +1882,16 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
"\n AND pg_catalog.pg_relation_is_publishable('%s')"
+ "\n AND NOT EXISTS (\n"
+ " SELECT 1\n"
+ " FROM pg_catalog.pg_publication_rel pr\n"
+ " WHERE pr.prpubid = p.oid AND\n"
+ " pr.prrelid = '%s')"
"\nORDER BY 1",
- oid);
+ oid, oid);
result = PSQLexec(buf.data);
if (result)
@@ -1912,6 +1917,42 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1928,6 +1969,7 @@ describeOneTableDetails(const char *schemaname,
free(footers[0]);
free(footers[1]);
+ free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6950,6 +6992,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7029,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7083,7 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,13 +7095,32 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
goto error_return;
}
}
+ if (puballsequences)
+ {
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index de547a8cb37..12d276cbb65 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3761,6 +3761,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 91377a6cde3..204aea610cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4465,14 +4465,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication relation */
Node *whereClause; /* qualifications */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4481,6 +4481,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4492,7 +4493,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4509,7 +4510,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..15c30ffeefc 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -473,6 +473,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -534,10 +535,64 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "public.regress_pub_seq0"
+ "public.regress_pub_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+ Sequence "public.regress_pub_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences3"
+
+RESET client_min_messages;
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+ Publication regress_pub_for_allsequences_alltables1
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.testpub_tbl1"
+Except sequences:
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..d472553c7cd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -223,6 +223,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,35 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+RESET client_min_messages;
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+RESET client_min_messages;
+
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..c03c77bb41e 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,81 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '100|t', 'initial test data replicated for seq2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence list
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub1, tap_pub2;
+ ALTER SUBSCRIPTION tap_sub REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq1 is excluded in tap_pub1 but included in tap_pub2, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq2 is excluded in tap_pub2 but included in tap_pub1, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+$node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8cf40c87043..a04d7de3cdc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2473,8 +2473,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
[application/octet-stream] v8-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLICA.patch (26.9K, ../../CANhcyEVCJOZTH9GL2wCOA+ea5uM_yqemUu2vAG-chNuA29HuJA@mail.gmail.com/3-v8-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLICA.patch)
download | inline diff:
From ebbf3128806c52889bfb302141e01701513a05dc Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Mon, 11 May 2026 10:59:28 +0530
Subject: [PATCH v8 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 41 +++-
src/backend/catalog/pg_publication.c | 31 ++-
src/backend/commands/publicationcmds.c | 263 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 16 ++
src/test/regress/sql/publication.sql | 5 +
7 files changed, 234 insertions(+), 142 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index aa32bb169e9..15b97efd566 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -75,7 +79,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
The third variant either modifies the included tables/schemas
or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
<literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -277,6 +285,25 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
tables:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's EXCEPT clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Reset the publication to be a ALL SEQUENCES publication with no excluded
+ sequences:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's EXCEPT clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5fd2c2795ab..686441fcab9 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -954,6 +954,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -973,8 +975,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION && relkind == RELKIND_RELATION) ||
+ (pubrelkind == RELKIND_RELATION && relkind == RELKIND_PARTITIONED_TABLE) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -998,7 +1007,7 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,12 +1015,13 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
Assert(GetPublication(pubid)->alltables ||
GetPublication(pubid)->allsequences);
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1065,7 +1075,7 @@ GetAllTablesPublications(void)
* it excludes sequences mentioned in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1074,19 +1084,20 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
/* EXCEPT filtering applies to tables and sequences */
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index e97578ddd75..b501d81ce7a 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1251,26 +1251,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Recreate list of tables/sequences to be dropped from the publication.
+ * To recreate the relation list for the publication, look for existing
+ * relations that do not need to be dropped.
+ *
+ * 'rels' contains the given list of relations, and 'oldrelids' contains
+ * the OIDs of existing relations in the publication identified by 'pubid'.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if(found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object (PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
* Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * possible that user has not specified any tables or sequences in which
+ * case we need to remove all the existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1284,29 +1389,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ SEQUENCES mode, relations are tracked as
+ * exclusions (EXCEPT clause). Fetch the current excluded
+ * relations so they can be reconciled with the specified EXCEPT
+ * list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1319,118 +1428,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1706,8 +1722,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /* EXCEPT clause is not supported with ALTER PUBLICATION */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1731,8 +1748,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2087,10 +2104,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 12d276cbb65..aaf03e3a94a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 15c30ffeefc..26b2f57fd4f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -562,16 +562,32 @@ Included in publications:
Excluded from publications:
"regress_pub_forallsequences3"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_pub_seq0"
+
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d472553c7cd..998ae3e4e4f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -261,14 +261,19 @@ CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUEN
-- Check that the sequence description shows the publications where it is listed
-- in the EXCEPT clause
\d+ regress_pub_seq0
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-02 00:07 ` Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
1 sibling, 1 reply; 99+ messages in thread
From: Peter Smith @ 2026-06-02 00:07 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Shlok,
Some review comments for patch v8-0002.
======
doc/src/sgml/ref/alter_publication.sgml
Examples.
1.
Everywhere in these example descriptions where you say "EXCEPT" or
"ALL SEQUENCES" etc, those should all be using SGML <literal> markup.
~~~
2.
+ <para>
+ Replace the sequence list in the publication's EXCEPT clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Reset the publication to be a ALL SEQUENCES publication with no excluded
+ sequences:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
2a.
Too wordy.
/in the publication's EXCEPT clause/in the EXCEPT clause/
~
2b.
The 1st example comment saying "Replace the..." doesn't really make
sense because reading from top-to-bottom, this was not even publishing
sequences.
So, I think the "ALTER PUBLICATION mypublication SET ALL SEQUENCES
EXCEPT (SEQUENCE seq1, seq2);" example should come *after* the "ALTER
PUBLICATION mypublication SET ALL SEQUENCES;" example.
~~~
3.
+ <para>
+ Replace the table and sequence list in the publication's EXCEPT clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users,
departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
Too wordy. Should be plural.
/in the publication's EXCEPT clause:/in the EXCEPT clauses:/
======
src/backend/catalog/pg_publication.c
GetIncludedPublicationRelations:
On Wed, May 27, 2026 at 11:03 PM Shlok Kyal <[email protected]> wrote:
>
> On Wed, 27 May 2026 at 09:08, Peter Smith <[email protected]> wrote:
> >
> > GetIncludedPublicationRelations:
> >
> > 4.
> > /*
> > * Gets list of relation oids that are associated with a publication.
> > *
> > * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
> > * should use GetAllPublicationRelations().
> > */
> > List *
> > GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
> > {
> > Assert(!GetPublication(pubid)->alltables);
> >
> > return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
> > }
> >
> > The comment does not match the code/assert. Shouldn't it also do
> > assert !allsequences?
> >
> This function can be called for ALL SEQUENCES publication. For example
> 'pg_get_publication_tables', 'InvalidatePubRelSyncCache', etc can call
> this function for all sequence publications, but in this case the list
> returned is empty. So, there is no overall impact.
> So, we should not add an 'assert !allsequences'.
Hmm. The reply seems contrary to the function comment that says "This
should only be used FOR TABLE publications", so if not going to add an
Assert then doesn't the function comment need fixing?
======
src/backend/commands/publicationcmds.c
get_delete_rels:
5.
/*
- * Add or remove table to/from publication.
+ * Recreate list of tables/sequences to be dropped from the publication.
+ * To recreate the relation list for the publication, look for existing
+ * relations that do not need to be dropped.
+ *
+ * 'rels' contains the given list of relations, and 'oldrelids' contains
+ * the OIDs of existing relations in the publication identified by 'pubid'.
+ */
Why say "recreate" everywhere? Can it just say "Returns the list of
...." instead of "Recreate ...".
Also, I think this function comment needs to explain in more detail
that this is called during ALTER SET. IIUC, the 'rels' is the reids
you want to end up with in the publication; so those need to be
compared with the 'oldrelids' to find which old ones are not wanted
anymore. Those unwanted ones are what the function returns.
~~~
AlterPublicationRelations:
6.
/*
* Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * possible that user has not specified any tables or sequences in which
+ * case we need to remove all the existing tables and sequences.
*/
Maybe this can be reworded more simply:
SUGGESTION
/*
* Nothing to do if no objects were specified, unless this is a SET
* command, which may need to remove all existing tables and sequences.
*/
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-06-05 07:44 ` Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-12 05:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
0 siblings, 2 replies; 99+ messages in thread
From: Shlok Kyal @ 2026-06-05 07:44 UTC (permalink / raw)
To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, 2 Jun 2026 at 05:37, Peter Smith <[email protected]> wrote:
>
> Hi Shlok,
>
> Some review comments for patch v8-0002.
>
> ======
> doc/src/sgml/ref/alter_publication.sgml
>
> Examples.
>
> 1.
> Everywhere in these example descriptions where you say "EXCEPT" or
> "ALL SEQUENCES" etc, those should all be using SGML <literal> markup.
>
Fixed
> ~~~
>
> 2.
> + <para>
> + Replace the sequence list in the publication's EXCEPT clause:
> +<programlisting>
> +ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
> +</programlisting></para>
> +
> + <para>
> + Reset the publication to be a ALL SEQUENCES publication with no excluded
> + sequences:
> +<programlisting>
> +ALTER PUBLICATION mypublication SET ALL SEQUENCES;
> +</programlisting></para>
>
> 2a.
> Too wordy.
>
> /in the publication's EXCEPT clause/in the EXCEPT clause/
>
In the existing documentation we already have similar documentation:
<para>
Replace the table list in the publication's EXCEPT clause:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users,
departments);
</programlisting></para>
I think we should keep the wording consistent. Thoughts?
> ~
>
> 2b.
> The 1st example comment saying "Replace the..." doesn't really make
> sense because reading from top-to-bottom, this was not even publishing
> sequences.
>
> So, I think the "ALTER PUBLICATION mypublication SET ALL SEQUENCES
> EXCEPT (SEQUENCE seq1, seq2);" example should come *after* the "ALTER
> PUBLICATION mypublication SET ALL SEQUENCES;" example.
>
Fixed
> ~~~
>
> 3.
> + <para>
> + Replace the table and sequence list in the publication's EXCEPT clause:
> +<programlisting>
> +ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users,
> departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
> </programlisting></para>
>
> Too wordy. Should be plural.
>
> /in the publication's EXCEPT clause:/in the EXCEPT clauses:/
>
I have kept the wording the same. Reason same as 2(a).
I have made it plural.
> ======
> src/backend/catalog/pg_publication.c
>
> GetIncludedPublicationRelations:
>
> On Wed, May 27, 2026 at 11:03 PM Shlok Kyal <[email protected]> wrote:
> >
> > On Wed, 27 May 2026 at 09:08, Peter Smith <[email protected]> wrote:
> > >
> > > GetIncludedPublicationRelations:
> > >
> > > 4.
> > > /*
> > > * Gets list of relation oids that are associated with a publication.
> > > *
> > > * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
> > > * should use GetAllPublicationRelations().
> > > */
> > > List *
> > > GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
> > > {
> > > Assert(!GetPublication(pubid)->alltables);
> > >
> > > return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
> > > }
> > >
> > > The comment does not match the code/assert. Shouldn't it also do
> > > assert !allsequences?
> > >
> > This function can be called for ALL SEQUENCES publication. For example
> > 'pg_get_publication_tables', 'InvalidatePubRelSyncCache', etc can call
> > this function for all sequence publications, but in this case the list
> > returned is empty. So, there is no overall impact.
> > So, we should not add an 'assert !allsequences'.
>
> Hmm. The reply seems contrary to the function comment that says "This
> should only be used FOR TABLE publications", so if not going to add an
> Assert then doesn't the function comment need fixing?
>
Yes, the function comment is not correct and a patch was already
proposed for the same in [1]. But it is a stale state there.
Since this patch is related to ALL SEQUENCES and EXCEPT, I think we
can update it here. I have updated the comment.
> ======
> src/backend/commands/publicationcmds.c
>
> get_delete_rels:
>
> 5.
> /*
> - * Add or remove table to/from publication.
> + * Recreate list of tables/sequences to be dropped from the publication.
> + * To recreate the relation list for the publication, look for existing
> + * relations that do not need to be dropped.
> + *
> + * 'rels' contains the given list of relations, and 'oldrelids' contains
> + * the OIDs of existing relations in the publication identified by 'pubid'.
> + */
>
> Why say "recreate" everywhere? Can it just say "Returns the list of
> ...." instead of "Recreate ...".
>
> Also, I think this function comment needs to explain in more detail
> that this is called during ALTER SET. IIUC, the 'rels' is the reids
> you want to end up with in the publication; so those need to be
> compared with the 'oldrelids' to find which old ones are not wanted
> anymore. Those unwanted ones are what the function returns.
>
I have reworded the comment
> ~~~
>
> AlterPublicationRelations:
>
> 6.
> /*
> * Nothing to do if no objects, except in SET: for that it is quite
> - * possible that user has not specified any tables in which case we need
> - * to remove all the existing tables.
> + * possible that user has not specified any tables or sequences in which
> + * case we need to remove all the existing tables and sequences.
> */
>
> Maybe this can be reworded more simply:
>
> SUGGESTION
> /*
> * Nothing to do if no objects were specified, unless this is a SET
> * command, which may need to remove all existing tables and sequences.
> */
Fixed
[1]:https://www.postgresql.org/message-id/CANhcyEUkV-T6cK142w9wfME9nobFHOvn1f4itJLMG-oR4QoPbQ%40mail.gma...
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v9-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLICA.patch (27.4K, ../../CANhcyEU=Vi2oNRWTSph3x2884J7aTt5BTb4AzwmmY0uQAsEMMg@mail.gmail.com/2-v9-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLICA.patch)
download | inline diff:
From 63af13ddcc95797a111cca70f5b42d3d2b4eab0a Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Mon, 11 May 2026 10:59:28 +0530
Subject: [PATCH v9 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 43 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 265 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 16 ++
src/test/regress/sql/publication.sql | 5 +
7 files changed, 240 insertions(+), 145 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index aa32bb169e9..b23191c7aa3 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -75,7 +79,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
The third variant either modifies the included tables/schemas
or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
<literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -277,6 +285,27 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
tables:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be a <literal>ALL SEQUENCES</literal> publication
+ with no excluded sequences:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5fd2c2795ab..bdf2836236b 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -954,6 +954,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -973,8 +975,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION && relkind == RELKIND_RELATION) ||
+ (pubrelkind == RELKIND_RELATION && relkind == RELKIND_PARTITIONED_TABLE) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -990,15 +999,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,12 +1016,13 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
Assert(GetPublication(pubid)->alltables ||
GetPublication(pubid)->allsequences);
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1065,7 +1076,7 @@ GetAllTablesPublications(void)
* it excludes sequences mentioned in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1074,19 +1085,20 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
/* EXCEPT filtering applies to tables and sequences */
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index e97578ddd75..957988c463d 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1251,26 +1251,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if(found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object (PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET command,
+ * which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1284,29 +1389,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ SEQUENCES mode, relations are tracked as
+ * exclusions (EXCEPT clause). Fetch the current excluded
+ * relations so they can be reconciled with the specified EXCEPT
+ * list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1319,118 +1428,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1706,8 +1722,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /* EXCEPT clause is not supported with ALTER PUBLICATION */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1731,8 +1748,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2087,10 +2104,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 12d276cbb65..aaf03e3a94a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 15c30ffeefc..26b2f57fd4f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -562,16 +562,32 @@ Included in publications:
Excluded from publications:
"regress_pub_forallsequences3"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_pub_seq0"
+
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d472553c7cd..998ae3e4e4f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -261,14 +261,19 @@ CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUEN
-- Check that the sequence description shows the publications where it is listed
-- in the EXCEPT clause
\d+ regress_pub_seq0
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
--
2.34.1
[application/octet-stream] v9-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLIC.patch (58.9K, ../../CANhcyEU=Vi2oNRWTSph3x2884J7aTt5BTb4AzwmmY0uQAsEMMg@mail.gmail.com/3-v9-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLIC.patch)
download | inline diff:
From 2e697f3847864580c71880f96ce986518cc32ed6 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v9 1/2] Support EXCEPT for ALL SEQUENCES in CREATE PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 6 +-
doc/src/sgml/ref/create_publication.sgml | 37 ++++++--
src/backend/catalog/pg_publication.c | 68 ++++++++-----
src/backend/commands/publicationcmds.c | 109 ++++++++++++---------
src/backend/parser/gram.y | 111 ++++++++++++++--------
src/bin/pg_dump/pg_dump.c | 56 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 +++++
src/bin/psql/describe.c | 74 +++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 11 ++-
src/test/regress/expected/publication.out | 59 +++++++++++-
src/test/regress/sql/publication.sql | 28 +++++-
src/test/subscription/t/037_except.pl | 75 +++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 534 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 9e7868487de..eb0b66491d3 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -119,7 +119,11 @@
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index f82d640e6ca..df736e74d2f 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,15 +193,18 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
@@ -217,9 +224,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -547,11 +555,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..5fd2c2795ab 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause,
+ * while 'targetrelkind' is the relkind of the relation being added.
+ * Error if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,14 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+ Assert(GetPublication(pubid)->alltables ||
+ GetPublication(pubid)->allsequences);
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1060,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * mentioned in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences mentioned in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1076,10 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ /* EXCEPT filtering applies to tables and sequences */
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..e97578ddd75 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -177,11 +177,12 @@ parse_publication_options(ParseState *pstate,
/*
* Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * PublicationRelation list.
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,27 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* Process EXCEPT sequence list */
+ if (stmt->for_all_sequences && exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
List *rels;
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ rels = OpenRelationList(excepttbls);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +984,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +992,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1270,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1281,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1304,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1378,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1425,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1666,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1698,16 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* EXCEPT clause is not supported with ALTER PUBLICATION */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1730,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1849,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1867,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2006,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2051,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2069,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..c7a499ab96d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a0f7f8e2168..dccce42f440 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,21 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4733,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+
+ if (++n_except == 1)
+ appendPQExpBufferStr(query, " EXCEPT (");
+ else
+ appendPQExpBufferStr(query, ", ");
+ appendPQExpBuffer(query, "SEQUENCE %s", fmtQualifiedDumpable(tbinfo));
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 3ee9fda50e4..4b2c88e23a2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3242,6 +3242,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4042,6 +4062,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e1449654f96..e3e7186e011 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,11 +1882,16 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
"\n AND pg_catalog.pg_relation_is_publishable('%s')"
+ "\n AND NOT EXISTS (\n"
+ " SELECT 1\n"
+ " FROM pg_catalog.pg_publication_rel pr\n"
+ " WHERE pr.prpubid = p.oid AND\n"
+ " pr.prrelid = '%s')"
"\nORDER BY 1",
- oid);
+ oid, oid);
result = PSQLexec(buf.data);
if (result)
@@ -1912,6 +1917,42 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1928,6 +1969,7 @@ describeOneTableDetails(const char *schemaname,
free(footers[0]);
free(footers[1]);
+ free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6950,6 +6992,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7029,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7083,7 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,13 +7095,32 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
goto error_return;
}
}
+ if (puballsequences)
+ {
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index de547a8cb37..12d276cbb65 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3761,6 +3761,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 91377a6cde3..204aea610cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4465,14 +4465,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication relation */
Node *whereClause; /* qualifications */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4481,6 +4481,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4492,7 +4493,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4509,7 +4510,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..15c30ffeefc 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -473,6 +473,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -534,10 +535,64 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "public.regress_pub_seq0"
+ "public.regress_pub_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+ Sequence "public.regress_pub_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences3"
+
+RESET client_min_messages;
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+ Publication regress_pub_for_allsequences_alltables1
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.testpub_tbl1"
+Except sequences:
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..d472553c7cd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -223,6 +223,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,35 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+RESET client_min_messages;
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+RESET client_min_messages;
+
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..c03c77bb41e 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,81 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '100|t', 'initial test data replicated for seq2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence list
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub1, tap_pub2;
+ ALTER SUBSCRIPTION tap_sub REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq1 is excluded in tap_pub1 but included in tap_pub2, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq2 is excluded in tap_pub2 but included in tap_pub1, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+$node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8cf40c87043..a04d7de3cdc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2473,8 +2473,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-12 05:25 ` Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
1 sibling, 1 reply; 99+ messages in thread
From: Peter Smith @ 2026-06-12 05:25 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Some review comments for v9-0001.
======
src/backend/catalog/pg_publication.c
GetExcludedPublicationTables:
1.
- Assert(GetPublication(pubid)->alltables);
+ Assert(GetPublication(pubid)->alltables ||
+ GetPublication(pubid)->allsequences);
Better to assign to a variable first, instead of calling GetPublication() 2x.
~~~
GetAllTablesPublications:
2.
- * For a FOR ALL TABLES publication, the returned list excludes
tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * mentioned in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences mentioned in the EXCEPT clause.
(2 times)
/mentioned/specified/ or
/mentioned/named/
~~~
3.
+ /* EXCEPT filtering applies to tables and sequences */
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
I don't think the comment is needed anymore. It was relevant before,
when there was a condition, but now there is no condition.
======
src/backend/parser/gram.y
preprocess_pubobj_list:
4.
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
...
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
Since you are changing these comments, you can uppercase them
in-passing for consistency.
/relation name/Relation name/
/convert it/Convert it/
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-06-15 10:13 ` Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Shlok Kyal @ 2026-06-15 10:13 UTC (permalink / raw)
To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, 12 Jun 2026 at 10:55, Peter Smith <[email protected]> wrote:
>
> Some review comments for v9-0001.
>
> ======
> src/backend/catalog/pg_publication.c
>
> GetExcludedPublicationTables:
>
> 1.
> - Assert(GetPublication(pubid)->alltables);
> + Assert(GetPublication(pubid)->alltables ||
> + GetPublication(pubid)->allsequences);
>
> Better to assign to a variable first, instead of calling GetPublication() 2x.
>
I agree. I've stored the result of GetPublication(pubid) in a local
variable and declared it under USE_ASSERT_CHECKING, since it is only
used by the assertion.
> ~~~
>
> GetAllTablesPublications:
>
> 2.
> - * For a FOR ALL TABLES publication, the returned list excludes
> tables mentioned
> - * in the EXCEPT clause.
> + * For a FOR ALL TABLES publication, the returned list excludes tables
> + * mentioned in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
> + * it excludes sequences mentioned in the EXCEPT clause.
>
> (2 times)
>
> /mentioned/specified/ or
> /mentioned/named/
>
> ~~~
>
> 3.
> + /* EXCEPT filtering applies to tables and sequences */
> + exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
> + PUBLICATION_PART_ROOT :
> + PUBLICATION_PART_LEAF);
>
> I don't think the comment is needed anymore. It was relevant before,
> when there was a condition, but now there is no condition.
>
> ======
> src/backend/parser/gram.y
>
> preprocess_pubobj_list:
>
> 4.
> - /* relation name or pubtable must be set for this type of object */
> - if (!pubobj->name && !pubobj->pubtable)
> + /* relation name or pubrelation must be set for this type of object */
> + if (!pubobj->name && !pubobj->pubrelation)
> ...
> - /* convert it to PublicationTable */
> - PublicationTable *pubtable = makeNode(PublicationTable);
> + /* convert it to PublicationRelation */
> + PublicationRelation *pubrelation = makeNode(PublicationRelation);
>
> Since you are changing these comments, you can uppercase them
> in-passing for consistency.
> /relation name/Relation name/
> /convert it/Convert it/
I have addressed the remaining comments in the v10 patches.
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v10-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (27.5K, ../../CANhcyEVDStqoR3qDSgsv5VEuBMzMX4YpdEfW-7VPX3c5Vf1YJA@mail.gmail.com/2-v10-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From 49df8a9ac4772cf83c2fc6abb77d4575df49613c Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Mon, 15 Jun 2026 15:17:01 +0530
Subject: [PATCH v10 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 43 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 265 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 16 ++
src/test/regress/sql/publication.sql | 5 +
7 files changed, 240 insertions(+), 145 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..c0886c5044a 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -75,7 +79,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
The third variant either modifies the included tables/schemas
or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
<literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -277,6 +285,27 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
with no excluded tables:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5f87b80a4be..4a42842896a 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -954,6 +954,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -973,8 +975,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION && relkind == RELKIND_RELATION) ||
+ (pubrelkind == RELKIND_RELATION && relkind == RELKIND_PARTITIONED_TABLE) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -990,15 +999,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,7 +1016,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1014,7 +1025,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1068,7 +1079,7 @@ GetAllTablesPublications(void)
* it excludes sequences specified in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1077,18 +1088,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index e97578ddd75..dbb958e5644 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1251,26 +1251,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1284,29 +1389,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ SEQUENCES mode, relations are tracked as
+ * exclusions (EXCEPT clause). Fetch the current excluded
+ * relations so they can be reconciled with the specified EXCEPT
+ * list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1319,118 +1428,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1706,8 +1722,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /* EXCEPT clause is not supported with ALTER PUBLICATION */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1731,8 +1748,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2087,10 +2104,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 12d276cbb65..aaf03e3a94a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 15c30ffeefc..26b2f57fd4f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -562,16 +562,32 @@ Included in publications:
Excluded from publications:
"regress_pub_forallsequences3"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_pub_seq0"
+
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d472553c7cd..998ae3e4e4f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -261,14 +261,19 @@ CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUEN
-- Check that the sequence description shows the publications where it is listed
-- in the EXCEPT clause
\d+ regress_pub_seq0
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
--
2.34.1
[application/octet-stream] v10-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (58.9K, ../../CANhcyEVDStqoR3qDSgsv5VEuBMzMX4YpdEfW-7VPX3c5Vf1YJA@mail.gmail.com/3-v10-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From 13a445559854adfee8d39693dac4597dfd535189 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v10 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 6 +-
doc/src/sgml/ref/create_publication.sgml | 37 ++++++--
src/backend/catalog/pg_publication.c | 70 +++++++++-----
src/backend/commands/publicationcmds.c | 109 ++++++++++++---------
src/backend/parser/gram.y | 111 ++++++++++++++--------
src/bin/pg_dump/pg_dump.c | 56 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 +++++
src/bin/psql/describe.c | 74 +++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 11 ++-
src/test/regress/expected/publication.out | 59 +++++++++++-
src/test/regress/sql/publication.sql | 28 +++++-
src/test/subscription/t/037_except.pl | 75 +++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 536 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 9e7868487de..eb0b66491d3 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -119,7 +119,11 @@
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index f82d640e6ca..df736e74d2f 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,15 +193,18 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
@@ -217,9 +224,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -547,11 +555,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..5f87b80a4be 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause,
+ * while 'targetrelkind' is the relkind of the relation being added.
+ * Error if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1063,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * mentioned in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1079,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..e97578ddd75 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -177,11 +177,12 @@ parse_publication_options(ParseState *pstate,
/*
* Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * PublicationRelation list.
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,27 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* Process EXCEPT sequence list */
+ if (stmt->for_all_sequences && exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
List *rels;
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ rels = OpenRelationList(excepttbls);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +984,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +992,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1270,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1281,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1304,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1378,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1425,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1666,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1698,16 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* EXCEPT clause is not supported with ALTER PUBLICATION */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1730,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1849,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1867,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2006,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2051,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2069,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..4c001ef2853 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a0f7f8e2168..dccce42f440 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,21 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4733,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+
+ if (++n_except == 1)
+ appendPQExpBufferStr(query, " EXCEPT (");
+ else
+ appendPQExpBufferStr(query, ", ");
+ appendPQExpBuffer(query, "SEQUENCE %s", fmtQualifiedDumpable(tbinfo));
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 3ee9fda50e4..4b2c88e23a2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3242,6 +3242,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4042,6 +4062,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index af3935b0078..d3f33630bad 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,11 +1882,16 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
"\n AND pg_catalog.pg_relation_is_publishable('%s')"
+ "\n AND NOT EXISTS (\n"
+ " SELECT 1\n"
+ " FROM pg_catalog.pg_publication_rel pr\n"
+ " WHERE pr.prpubid = p.oid AND\n"
+ " pr.prrelid = '%s')"
"\nORDER BY 1",
- oid);
+ oid, oid);
result = PSQLexec(buf.data);
if (result)
@@ -1912,6 +1917,42 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1928,6 +1969,7 @@ describeOneTableDetails(const char *schemaname,
free(footers[0]);
free(footers[1]);
+ free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6950,6 +6992,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7029,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7083,7 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,13 +7095,32 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
goto error_return;
}
}
+ if (puballsequences)
+ {
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index de547a8cb37..12d276cbb65 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3761,6 +3761,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 91377a6cde3..204aea610cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4465,14 +4465,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication relation */
Node *whereClause; /* qualifications */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4481,6 +4481,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4492,7 +4493,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4509,7 +4510,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..15c30ffeefc 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -473,6 +473,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -534,10 +535,64 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "public.regress_pub_seq0"
+ "public.regress_pub_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+ Sequence "public.regress_pub_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences3"
+
+RESET client_min_messages;
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+ Publication regress_pub_for_allsequences_alltables1
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.testpub_tbl1"
+Except sequences:
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..d472553c7cd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -223,6 +223,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,35 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+RESET client_min_messages;
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+RESET client_min_messages;
+
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..c03c77bb41e 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,81 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '100|t', 'initial test data replicated for seq2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence list
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub1, tap_pub2;
+ ALTER SUBSCRIPTION tap_sub REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq1 is excluded in tap_pub1 but included in tap_pub2, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq2 is excluded in tap_pub2 but included in tap_pub1, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+$node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f9eb23e52c9..074763867a2 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2474,8 +2474,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-17 07:06 ` Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Peter Smith @ 2026-06-17 07:06 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
////////////////////
Some review comments for v10-00001.
////////////////////
======
doc/src/sgml/logical-replication.sgml
1.
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
Yes, it is valid English to say "excluded from publication" (meaning
won't be published); however, in most other documentation about
EXCEPT, we refer to the PUBLICATION *object*, so it is worded like
"excluded from the publication".
So I think it should be /from publication/from the publication/
(fix in 2 places in this file)
======
src/backend/catalog/pg_publication.c
GetAllTablesPublications:
2.
> - * For a FOR ALL TABLES publication, the returned list excludes
> tables mentioned
> - * in the EXCEPT clause.
> + * For a FOR ALL TABLES publication, the returned list excludes tables
> + * mentioned in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
> + * it excludes sequences mentioned in the EXCEPT clause.
>
> (2 times)
>
> /mentioned/specified/ or
> /mentioned/named/
Please also fix the 1st "mentioned" in that comment.
======
src/backend/commands/publicationcmds.c
3.
+ /* Process EXCEPT sequence list */
+ if (stmt->for_all_sequences && exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
List *rels;
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ rels = OpenRelationList(excepttbls);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
I felt that the fragments for "Process EXCEPT sequence list" and
"Process EXCEPT table list" should look exactly the same, but they are
currently formatted slightly differently. Normally, you might not
write the first `if` as nested, but here I think consistency is more
important.
SUGGESTION
if (stmt->for_all_sequences)
{
/* Process EXCEPT sequence list */
if (exceptseqs != NIL)
{
List *rels = OpenRelationList(exceptseqs);
PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
CloseRelationList(rels);
}
}
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
if (excepttbls != NIL)
{
List *rels = OpenRelationList(excepttbls);
PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
CloseRelationList(rels);
}
...
}
======
src/bin/pg_dump/pg_dump.c
4.
+ {
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ }
This code is building the `except_sequences` list in a block guarded
by >= 190000. In fact, EXCEPT SEQUENCES won't exist until PG20. I'm
not sure if this code should have a 20000 check... It may be harmless
as-is if this can never happen for PG19.
======
src/bin/psql/describe.c
describeOneTableDetails:
5.
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
"\n AND pg_catalog.pg_relation_is_publishable('%s')"
+ "\n AND NOT EXISTS (\n"
+ " SELECT 1\n"
+ " FROM pg_catalog.pg_publication_rel pr\n"
+ " WHERE pr.prpubid = p.oid AND\n"
+ " pr.prrelid = '%s')"
"\nORDER BY 1",
- oid);
+ oid, oid);
5a.
IIUC, the "NOT EXISTS" part is for skipping the excluded sequences.
And, you don't say "AND pr.prexcept" because it is redundant since
the only way for a SEQUENCE to be individually in pg_publication_rel
is if it is an excluded sequence.
Is my understanding correct? I think it is too subtle -- it might be
better to say "AND pr.prexcept" even if it is not strictly needed.
~
5b.
Anyway, this code should not com into play for PG19, because excluded
sequences will be a PG20 feature... so I think that whole "AND NOT
EXISTS" part needs another check.
~~~
6.
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
This whole part will be a PG20 feature. I think you should add a
"TODO" reminder comment here to flag/remind to modify this to check >=
200000 as soon as the PG19 version is bumped.
~~~
describePublications:
7.
+ if (puballsequences)
+ {
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
Similar to the previous review comment. EXCEPT SEQUENCES is a PG20
feature, not PG19, so this should have a "TODO" comment reminder to
change the version check to 200000 when the PG version is bumped.
======
src/test/subscription/t/037_except.pl
8.
+# Subscribe to multiple publications with different EXCEPT sequence list
/list/lists/
////////////////////
Some review comments for patch v10-0002
////////////////////
======
doc/src/sgml/ref/alter_publication.sgml
Examples:
1.
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
Thanks for changing the above. "In passing", can you modify the "ALL
TABLES" text to use the same wording? Otherwise, the ALL TABLES text
will never be improved.
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-06-18 11:28 ` Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 11:39 ` Re: Support EXCEPT for ALL SEQUENCES publications solai v <[email protected]>
0 siblings, 2 replies; 99+ messages in thread
From: Shlok Kyal @ 2026-06-18 11:28 UTC (permalink / raw)
To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, 17 Jun 2026 at 12:37, Peter Smith <[email protected]> wrote:
>
> ////////////////////
> Some review comments for v10-00001.
> ////////////////////
>
> ======
> doc/src/sgml/logical-replication.sgml
>
> 1.
> <xref linkend="logical-replication-sequences"/>. When a publication is
> created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
> be explicitly excluded from publication using the
> - <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
> + <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
> + clause. Similarly, when a publication is created with
> + <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
> + explicitly excluded from publication using the
> + <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
> clause.
> </para>
>
> Yes, it is valid English to say "excluded from publication" (meaning
> won't be published); however, in most other documentation about
> EXCEPT, we refer to the PUBLICATION *object*, so it is worded like
> "excluded from the publication".
>
> So I think it should be /from publication/from the publication/
>
> (fix in 2 places in this file)
>
> ======
> src/backend/catalog/pg_publication.c
>
> GetAllTablesPublications:
>
> 2.
> > - * For a FOR ALL TABLES publication, the returned list excludes
> > tables mentioned
> > - * in the EXCEPT clause.
> > + * For a FOR ALL TABLES publication, the returned list excludes tables
> > + * mentioned in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
> > + * it excludes sequences mentioned in the EXCEPT clause.
> >
> > (2 times)
> >
> > /mentioned/specified/ or
> > /mentioned/named/
>
> Please also fix the 1st "mentioned" in that comment.
>
> ======
> src/backend/commands/publicationcmds.c
>
> 3.
> + /* Process EXCEPT sequence list */
> + if (stmt->for_all_sequences && exceptseqs != NIL)
> + {
> + List *rels = OpenRelationList(exceptseqs);
> +
> + PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
> + CloseRelationList(rels);
> + }
>
> if (stmt->for_all_tables)
> {
> /* Process EXCEPT table list */
> - if (exceptrelations != NIL)
> + if (excepttbls != NIL)
> {
> List *rels;
>
> - rels = OpenTableList(exceptrelations);
> - PublicationAddTables(puboid, rels, true, NULL);
> - CloseTableList(rels);
> + rels = OpenRelationList(excepttbls);
> + PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
> + CloseRelationList(rels);
>
> I felt that the fragments for "Process EXCEPT sequence list" and
> "Process EXCEPT table list" should look exactly the same, but they are
> currently formatted slightly differently. Normally, you might not
> write the first `if` as nested, but here I think consistency is more
> important.
>
> SUGGESTION
> if (stmt->for_all_sequences)
> {
> /* Process EXCEPT sequence list */
> if (exceptseqs != NIL)
> {
> List *rels = OpenRelationList(exceptseqs);
>
> PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
> CloseRelationList(rels);
> }
> }
>
> if (stmt->for_all_tables)
> {
> /* Process EXCEPT table list */
> if (excepttbls != NIL)
> {
> List *rels = OpenRelationList(excepttbls);
>
> PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
> CloseRelationList(rels);
> }
> ...
> }
>
> ======
> src/bin/pg_dump/pg_dump.c
>
> 4.
> + {
> + if (relkind == RELKIND_SEQUENCE)
> + simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
> + else
> + simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
> + }
>
> This code is building the `except_sequences` list in a block guarded
> by >= 190000. In fact, EXCEPT SEQUENCES won't exist until PG20. I'm
> not sure if this code should have a 20000 check... It may be harmless
> as-is if this can never happen for PG19.
>
Added a TODO to use version check for PG20
> ======
> src/bin/psql/describe.c
>
> describeOneTableDetails:
>
> 5.
> printfPQExpBuffer(&buf, "/* %s */\n",
> _("Get publications containing this sequence"));
> - appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
> + appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
> "\nWHERE p.puballsequences"
> "\n AND pg_catalog.pg_relation_is_publishable('%s')"
> + "\n AND NOT EXISTS (\n"
> + " SELECT 1\n"
> + " FROM pg_catalog.pg_publication_rel pr\n"
> + " WHERE pr.prpubid = p.oid AND\n"
> + " pr.prrelid = '%s')"
> "\nORDER BY 1",
> - oid);
> + oid, oid);
>
> 5a.
> IIUC, the "NOT EXISTS" part is for skipping the excluded sequences.
> And, you don't say "AND pr.prexcept" because it is redundant since
> the only way for a SEQUENCE to be individually in pg_publication_rel
> is if it is an excluded sequence.
>
> Is my understanding correct? I think it is too subtle -- it might be
> better to say "AND pr.prexcept" even if it is not strictly needed.
>
Yes, your understanding is correct.
For ALL SEQUENCES publication, the pg_publication_rel will have the
entry only if the sequence is specified in the EXCEPT list.
I didn't add "AND pr.prexcept" because the corresponding code for ALL
TABLES publications also does not use "AND pr.prexcept".
```
SELECT pubname\n"
" , NULL\n"
" , NULL\n"
"FROM pg_catalog.pg_publication p\n"
"WHERE p.puballtables AND
pg_catalog.pg_relation_is_publishable('%s')\n"
" AND NOT EXISTS (\n"
" SELECT 1\n"
" FROM
pg_catalog.pg_publication_rel pr\n"
" WHERE pr.prpubid = p.oid AND\n"
" (pr.prrelid = '%s' OR
pr.prrelid = pg_catalog.pg_partition_root('%s')))\n"
"ORDER BY 1;",
```
I think it will be consistent with EXCEPT (TABLE .. ) case. So, I
think we should not add "AND pr.prexcept".
> ~
>
> 5b.
> Anyway, this code should not com into play for PG19, because excluded
> sequences will be a PG20 feature... so I think that whole "AND NOT
> EXISTS" part needs another check.
>
Separated and added a TODO to change the version.
> ~~~
>
> 6.
> + /* Print publications where the sequence is in the EXCEPT clause */
> + if (pset.sversion >= 190000)
> + {
> + printfPQExpBuffer(&buf,
> + "SELECT p.pubname\n"
> + "FROM pg_catalog.pg_publication p\n"
> + "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
> + "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
> + "ORDER BY 1;", oid);
>
> This whole part will be a PG20 feature. I think you should add a
> "TODO" reminder comment here to flag/remind to modify this to check >=
> 200000 as soon as the PG19 version is bumped.
>
Added
> ~~~
>
> describePublications:
>
> 7.
> + if (puballsequences)
> + {
> + if (pset.sversion >= 190000)
> + {
> + /* Get sequences in the EXCEPT clause for this publication */
> + printfPQExpBuffer(&buf, "/* %s */\n",
> + _("Get sequences excluded by this publication"));
> + printfPQExpBuffer(&buf,
> + "SELECT n.nspname || '.' || c.relname\n"
> + "FROM pg_catalog.pg_class c\n"
> + " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
> + " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
> + "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
> + "ORDER BY 1", pubid);
> + if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
> + true, &cont))
> + goto error_return;
> + }
> + }
>
> Similar to the previous review comment. EXCEPT SEQUENCES is a PG20
> feature, not PG19, so this should have a "TODO" comment reminder to
> change the version check to 200000 when the PG version is bumped.
>
Added a TODO
> ======
> src/test/subscription/t/037_except.pl
>
> 8.
> +# Subscribe to multiple publications with different EXCEPT sequence list
>
> /list/lists/
>
>
> ////////////////////
> Some review comments for patch v10-0002
> ////////////////////
>
> ======
> doc/src/sgml/ref/alter_publication.sgml
>
> Examples:
>
> 1.
> + Reset the publication to be <literal>ALL SEQUENCES</literal> with no
> + exclusions:
>
> Thanks for changing the above. "In passing", can you modify the "ALL
> TABLES" text to use the same wording? Otherwise, the ALL TABLES text
> will never be improved.
I have also addressed the remaining comments and attached the updated v11 patch.
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v11-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (59.4K, ../../CANhcyEW+aJ1rAAodQZDvCd-WpQ+b8r5wBs8H+mNdQS8URmkODg@mail.gmail.com/2-v11-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From 16001e9e8d8ed144e7821cd52aa4bff9836df321 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v11 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 8 +-
doc/src/sgml/ref/create_publication.sgml | 37 +++++--
src/backend/catalog/pg_publication.c | 70 +++++++++-----
src/backend/commands/publicationcmds.c | 112 +++++++++++++---------
src/backend/parser/gram.y | 111 +++++++++++++--------
src/bin/pg_dump/pg_dump.c | 57 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 +++++
src/bin/psql/describe.c | 84 ++++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 11 ++-
src/test/regress/expected/publication.out | 59 +++++++++++-
src/test/regress/sql/publication.sql | 28 +++++-
src/test/subscription/t/037_except.pl | 75 +++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 550 insertions(+), 154 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 9e7868487de..f55ca9ac71a 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -118,8 +118,12 @@
synchronized at any time. For more information, see
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
- be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ be explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 85cfcaddafa..a8bd48757b7 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,15 +193,18 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
@@ -223,9 +230,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -553,11 +561,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..f03b85fa34b 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause,
+ * while 'targetrelkind' is the relkind of the relation being added.
+ * Error if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1063,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1079,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..76cd8f58f13 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -177,11 +177,12 @@ parse_publication_options(ParseState *pstate,
/*
* Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * PublicationRelation list.
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,30 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
List *rels;
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ rels = OpenRelationList(excepttbls);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +987,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +995,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1273,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1284,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1307,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1381,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1428,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1669,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1701,16 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* EXCEPT clause is not supported with ALTER PUBLICATION */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1733,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1852,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1870,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2009,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2054,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2072,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..4c001ef2853 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a0f7f8e2168..99175c78b2c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,22 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ /* TODO : Add a version check for PG 20 */
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4734,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+
+ if (++n_except == 1)
+ appendPQExpBufferStr(query, " EXCEPT (");
+ else
+ appendPQExpBufferStr(query, ", ");
+ appendPQExpBuffer(query, "SEQUENCE %s", fmtQualifiedDumpable(tbinfo));
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..c2c1d515674 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3283,6 +3283,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4082,6 +4102,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index af3935b0078..a4bec59af19 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,12 +1882,23 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
- "\n AND pg_catalog.pg_relation_is_publishable('%s')"
- "\nORDER BY 1",
+ "\n AND pg_catalog.pg_relation_is_publishable('%s')",
oid);
+ /* TODO : Add a version check for PG 20 */
+ if (pset.sversion >= 190000)
+ {
+ appendPQExpBuffer(&buf, "\n AND NOT EXISTS (\n"
+ " SELECT 1\n"
+ " FROM pg_catalog.pg_publication_rel pr\n"
+ " WHERE pr.prpubid = p.oid AND\n"
+ " pr.prrelid = '%s')", oid);
+ }
+
+ appendPQExpBuffer(&buf, "\nORDER BY 1");
+
result = PSQLexec(buf.data);
if (result)
{
@@ -1912,6 +1923,43 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* TODO : Add a version check for PG 20 */
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1928,6 +1976,7 @@ describeOneTableDetails(const char *schemaname,
free(footers[0]);
free(footers[1]);
+ free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6950,6 +6999,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7036,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7090,7 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,13 +7102,33 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
goto error_return;
}
}
+ if (puballsequences)
+ {
+ /* TODO : Add a version check for PG 20 */
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index de547a8cb37..12d276cbb65 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3761,6 +3761,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4133c404a6b..6870daab5fe 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4460,14 +4460,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication relation */
Node *whereClause; /* qualifications */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4476,6 +4476,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4487,7 +4488,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4504,7 +4505,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..15c30ffeefc 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -473,6 +473,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -534,10 +535,64 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "public.regress_pub_seq0"
+ "public.regress_pub_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+ Sequence "public.regress_pub_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences3"
+
+RESET client_min_messages;
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+ Publication regress_pub_for_allsequences_alltables1
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.testpub_tbl1"
+Except sequences:
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..d472553c7cd 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -223,6 +223,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,35 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+RESET client_min_messages;
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+RESET client_min_messages;
+
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..b559076a4d1 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,81 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '100|t', 'initial test data replicated for seq2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence lists
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub1, tap_pub2;
+ ALTER SUBSCRIPTION tap_sub REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq1 is excluded in tap_pub1 but included in tap_pub2, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq2 is excluded in tap_pub2 but included in tap_pub1, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+$node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f9eb23e52c9..074763867a2 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2474,8 +2474,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
[application/octet-stream] v11-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (27.7K, ../../CANhcyEW+aJ1rAAodQZDvCd-WpQ+b8r5wBs8H+mNdQS8URmkODg@mail.gmail.com/3-v11-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From 6e18bb1cd9eb04b3fb963ecee440ad6c70d8034e Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Mon, 15 Jun 2026 15:17:01 +0530
Subject: [PATCH v11 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 47 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 265 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 16 ++
src/test/regress/sql/publication.sql | 5 +
7 files changed, 242 insertions(+), 147 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..8e3f632e3b7 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -75,7 +79,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
The third variant either modifies the included tables/schemas
or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
<literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -273,10 +281,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
</programlisting></para>
<para>
- Reset the publication to be a <literal>FOR ALL TABLES</literal> publication
- with no excluded tables:
+ Reset the publication to be <literal>FOR ALL TABLES</literal> with no
+ exclusions:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index f03b85fa34b..34a8ce4d728 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -954,6 +954,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -973,8 +975,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION && relkind == RELKIND_RELATION) ||
+ (pubrelkind == RELKIND_RELATION && relkind == RELKIND_PARTITIONED_TABLE) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -990,15 +999,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,7 +1016,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1014,7 +1025,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1068,7 +1079,7 @@ GetAllTablesPublications(void)
* it excludes sequences specified in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1077,18 +1088,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 76cd8f58f13..16b004a7fd3 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1254,26 +1254,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1287,29 +1392,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ SEQUENCES mode, relations are tracked as
+ * exclusions (EXCEPT clause). Fetch the current excluded
+ * relations so they can be reconciled with the specified EXCEPT
+ * list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1322,118 +1431,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1709,8 +1725,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /* EXCEPT clause is not supported with ALTER PUBLICATION */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1734,8 +1751,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2090,10 +2107,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 12d276cbb65..aaf03e3a94a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 15c30ffeefc..26b2f57fd4f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -562,16 +562,32 @@ Included in publications:
Excluded from publications:
"regress_pub_forallsequences3"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_pub_seq0"
+
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d472553c7cd..998ae3e4e4f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -261,14 +261,19 @@ CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUEN
-- Check that the sequence description shows the publications where it is listed
-- in the EXCEPT clause
\d+ regress_pub_seq0
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-22 04:35 ` Peter Smith <[email protected]>
2026-06-22 21:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
1 sibling, 2 replies; 99+ messages in thread
From: Peter Smith @ 2026-06-22 04:35 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Shlok.
Some review comments for v11-0001 and v11-0002.
//////////
v11-0001
======
doc/src/sgml/ref/create_publication.sgml
(EXCEPT)
1.
The recent commit 77b6dd9 added some more information to say:
---
Once a table is excluded, the exclusion applies to that table
regardless of its name or schema. Renaming the table or moving it to
another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
not remove the exclusion.
---
1a
AFAIK this same rule (the exclusion follows the object regardless of
the schema) is going to apply also for excluding sequences. So, the
docs should be updated to say something similar about behaviour for
sequences.
~
1b.
Maybe you need to add another test case to exclude some sequence, then
alter the sequence's schema, then verify that the moved sequence is
still excluded.
~~~
On Thu, Jun 18, 2026 at 11:29 PM Shlok Kyal <[email protected]> wrote:
>
> On Wed, 17 Jun 2026 at 12:37, Peter Smith <[email protected]> wrote:
...
> > ======
> > src/bin/psql/describe.c
> >
> > describeOneTableDetails:
> >
> > 5.
> > printfPQExpBuffer(&buf, "/* %s */\n",
> > _("Get publications containing this sequence"));
> > - appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
> > + appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
> > "\nWHERE p.puballsequences"
> > "\n AND pg_catalog.pg_relation_is_publishable('%s')"
> > + "\n AND NOT EXISTS (\n"
> > + " SELECT 1\n"
> > + " FROM pg_catalog.pg_publication_rel pr\n"
> > + " WHERE pr.prpubid = p.oid AND\n"
> > + " pr.prrelid = '%s')"
> > "\nORDER BY 1",
> > - oid);
> > + oid, oid);
> >
> > 5a.
> > IIUC, the "NOT EXISTS" part is for skipping the excluded sequences.
> > And, you don't say "AND pr.prexcept" because it is redundant since
> > the only way for a SEQUENCE to be individually in pg_publication_rel
> > is if it is an excluded sequence.
> >
> > Is my understanding correct? I think it is too subtle -- it might be
> > better to say "AND pr.prexcept" even if it is not strictly needed.
> >
> Yes, your understanding is correct.
> For ALL SEQUENCES publication, the pg_publication_rel will have the
> entry only if the sequence is specified in the EXCEPT list.
> I didn't add "AND pr.prexcept" because the corresponding code for ALL
> TABLES publications also does not use "AND pr.prexcept".
...
> I think it will be consistent with EXCEPT (TABLE .. ) case. So, I
> think we should not add "AND pr.prexcept".
That’s OK, but I felt if you are going to omit the 'prexcept', then
perhaps there should be a comment to explain (in all places) why it is
ok to do that.
//////////
v11-0002.
======
doc/src/sgml/ref/alter_publication.sgml
1.
...or marks the publication as FOR ALL SEQUENCES or FOR ALL TABLES,
optionally using EXCEPT to exclude specific tables or sequences.
~
That current documentation sentence seems muddled because the
publication types are not in the same as the order of the excluded
types.
IMO, alternatives below are better:
a) ...FOR ALL SEQUENCES or FOR ALL TABLES, optionally using EXCEPT to
exclude specific sequences or tables.
b) ...FOR ALL TABLES or FOR ALL SEQUENCES, optionally using EXCEPT to
exclude specific tables or sequences.
(I preferred b, since everywhere else seems to be ordered
tables/sequences not sequences/tables.).
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-06-22 21:03 ` Peter Smith <[email protected]>
1 sibling, 0 replies; 99+ messages in thread
From: Peter Smith @ 2026-06-22 21:03 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Shlok.
FYI, I tested that the exclusion of a SEQUENCE follows the sequence
object around the same as a table excluded from FOR ALL TABLES does.
> 1.
> The recent commit 77b6dd9 added some more information to say:
>
> ---
> Once a table is excluded, the exclusion applies to that table
> regardless of its name or schema. Renaming the table or moving it to
> another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
> not remove the exclusion.
> ---
>
> 1a
> AFAIK this same rule (the exclusion follows the object regardless of
> the schema) is going to apply also for excluding sequences. So, the
> docs should be updated to say something similar about behaviour for
> sequences.
>
> ~
>
> 1b.
> Maybe you need to add another test case to exclude some sequence, then
> alter the sequence's schema, then verify that the moved sequence is
> still excluded.
>
test_pub=# CREATE PUBLICATION pub1 FOR ALL TABLES EXCEPT (TABLE
PUBT1), ALL SEQUENCES EXCEPT (SEQUENCE PUBSEQ);
CREATE PUBLICATION
test_pub=# \dRp+ pub1
Publication pub1
Owner | All tables | All sequences | Inserts | Updates | Deletes |
Truncates | Generated columns | Via root | Descri
ption
----------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------
------
postgres | t | t | t | t | t |
t | none | f |
Except tables:
"public.pubt1"
Except sequences:
"public.pubseq"
-- Now move the excluded sequence to another schema and see that it is
still excluded
test_pub=# alter sequence pubseq set schema myschema;
ALTER SEQUENCE
test_pub=# \dRp+ pub1
Publication pub1
Owner | All tables | All sequences | Inserts | Updates | Deletes |
Truncates | Generated columns | Via root | Descri
ption
----------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------
------
postgres | t | t | t | t | t |
t | none | f |
Except tables:
"public.pubt1"
Except sequences:
"myschema.pubseq"
-- Now rename the excluded sequence and see that it is still excluded
test_pub=# alter sequence myschema.pubseq rename to pubseq2;
ALTER SEQUENCE
test_pub=# \dRp+ pub1
Publication pub1
Owner | All tables | All sequences | Inserts | Updates | Deletes |
Truncates | Generated columns | Via root | Descri
ption
----------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------
------
postgres | t | t | t | t | t |
t | none | f |
Except tables:
"public.pubt1"
Except sequences:
"myschema.pubseq2"
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-06-23 09:33 ` Shlok Kyal <[email protected]>
2026-06-24 06:04 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
1 sibling, 2 replies; 99+ messages in thread
From: Shlok Kyal @ 2026-06-23 09:33 UTC (permalink / raw)
To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, 22 Jun 2026 at 10:05, Peter Smith <[email protected]> wrote:
>
> Hi Shlok.
>
> Some review comments for v11-0001 and v11-0002.
>
> //////////
> v11-0001
>
> ======
> doc/src/sgml/ref/create_publication.sgml
>
> (EXCEPT)
>
> 1.
> The recent commit 77b6dd9 added some more information to say:
>
> ---
> Once a table is excluded, the exclusion applies to that table
> regardless of its name or schema. Renaming the table or moving it to
> another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
> not remove the exclusion.
> ---
>
> 1a
> AFAIK this same rule (the exclusion follows the object regardless of
> the schema) is going to apply also for excluding sequences. So, the
> docs should be updated to say something similar about behaviour for
> sequences.
>
Modified
> ~
>
> 1b.
> Maybe you need to add another test case to exclude some sequence, then
> alter the sequence's schema, then verify that the moved sequence is
> still excluded.
>
Added the test
> ~~~
> On Thu, Jun 18, 2026 at 11:29 PM Shlok Kyal <[email protected]> wrote:
> >
> > On Wed, 17 Jun 2026 at 12:37, Peter Smith <[email protected]> wrote:
> ...
> > > ======
> > > src/bin/psql/describe.c
> > >
> > > describeOneTableDetails:
> > >
> > > 5.
> > > printfPQExpBuffer(&buf, "/* %s */\n",
> > > _("Get publications containing this sequence"));
> > > - appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
> > > + appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
> > > "\nWHERE p.puballsequences"
> > > "\n AND pg_catalog.pg_relation_is_publishable('%s')"
> > > + "\n AND NOT EXISTS (\n"
> > > + " SELECT 1\n"
> > > + " FROM pg_catalog.pg_publication_rel pr\n"
> > > + " WHERE pr.prpubid = p.oid AND\n"
> > > + " pr.prrelid = '%s')"
> > > "\nORDER BY 1",
> > > - oid);
> > > + oid, oid);
> > >
> > > 5a.
> > > IIUC, the "NOT EXISTS" part is for skipping the excluded sequences.
> > > And, you don't say "AND pr.prexcept" because it is redundant since
> > > the only way for a SEQUENCE to be individually in pg_publication_rel
> > > is if it is an excluded sequence.
> > >
> > > Is my understanding correct? I think it is too subtle -- it might be
> > > better to say "AND pr.prexcept" even if it is not strictly needed.
> > >
> > Yes, your understanding is correct.
> > For ALL SEQUENCES publication, the pg_publication_rel will have the
> > entry only if the sequence is specified in the EXCEPT list.
> > I didn't add "AND pr.prexcept" because the corresponding code for ALL
> > TABLES publications also does not use "AND pr.prexcept".
> ...
> > I think it will be consistent with EXCEPT (TABLE .. ) case. So, I
> > think we should not add "AND pr.prexcept".
>
> That’s OK, but I felt if you are going to omit the 'prexcept', then
> perhaps there should be a comment to explain (in all places) why it is
> ok to do that.
>
Added the comments
> //////////
> v11-0002.
>
> ======
> doc/src/sgml/ref/alter_publication.sgml
>
> 1.
> ...or marks the publication as FOR ALL SEQUENCES or FOR ALL TABLES,
> optionally using EXCEPT to exclude specific tables or sequences.
>
> ~
>
> That current documentation sentence seems muddled because the
> publication types are not in the same as the order of the excluded
> types.
>
> IMO, alternatives below are better:
> a) ...FOR ALL SEQUENCES or FOR ALL TABLES, optionally using EXCEPT to
> exclude specific sequences or tables.
> b) ...FOR ALL TABLES or FOR ALL SEQUENCES, optionally using EXCEPT to
> exclude specific tables or sequences.
>
> (I preferred b, since everywhere else seems to be ordered
> tables/sequences not sequences/tables.).
>
I also think (b) is better.
I have addressed the comments and attached the updated v12 patch
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v12-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (27.8K, ../../CANhcyEWfzDRCiny6A_swAD7WUgdb37-TA4xTAUQ3JJXK9UPBZQ@mail.gmail.com/2-v12-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From b294b39449ebf35e2a92be9491396b9178be8c4b Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 23 Jun 2026 14:29:14 +0530
Subject: [PATCH v12 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 51 ++++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 265 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 16 ++
src/test/regress/sql/publication.sql | 6 +
7 files changed, 245 insertions(+), 149 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..d82ff3079db 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -73,9 +77,9 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
The third variant either modifies the included tables/schemas
- or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
- <literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ or marks the publication as <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL SEQUENCES</literal>, optionally using
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -273,10 +281,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
</programlisting></para>
<para>
- Reset the publication to be a <literal>FOR ALL TABLES</literal> publication
- with no excluded tables:
+ Reset the publication to be <literal>FOR ALL TABLES</literal> with no
+ exclusions:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index f03b85fa34b..34a8ce4d728 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -954,6 +954,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -973,8 +975,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION && relkind == RELKIND_RELATION) ||
+ (pubrelkind == RELKIND_RELATION && relkind == RELKIND_PARTITIONED_TABLE) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -990,15 +999,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,7 +1016,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1014,7 +1025,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1068,7 +1079,7 @@ GetAllTablesPublications(void)
* it excludes sequences specified in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1077,18 +1088,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 76cd8f58f13..16b004a7fd3 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1254,26 +1254,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1287,29 +1392,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ SEQUENCES mode, relations are tracked as
+ * exclusions (EXCEPT clause). Fetch the current excluded
+ * relations so they can be reconciled with the specified EXCEPT
+ * list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1322,118 +1431,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1709,8 +1725,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /* EXCEPT clause is not supported with ALTER PUBLICATION */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1734,8 +1751,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2090,10 +2107,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 12d276cbb65..aaf03e3a94a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index c1a5fea2a96..206aeb181c6 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -575,16 +575,32 @@ Except sequences:
"pub_test.regress_pub_seq2"
"public.regress_pub_seq0"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_pub_seq0"
+
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 0423a77d5de..d72fc40d404 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -266,14 +266,20 @@ CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUEN
-- another schema.
ALTER SEQUENCE regress_pub_seq2 SET SCHEMA pub_test;
\dRp+ regress_pub_forallsequences3
+
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
--
2.34.1
[application/octet-stream] v12-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (61.4K, ../../CANhcyEWfzDRCiny6A_swAD7WUgdb37-TA4xTAUQ3JJXK9UPBZQ@mail.gmail.com/3-v12-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From df6693da56d37a99927e0e0f56189743e957b6da Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v12 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 8 +-
doc/src/sgml/ref/create_publication.sgml | 46 ++++++---
src/backend/catalog/pg_publication.c | 70 +++++++++-----
src/backend/commands/publicationcmds.c | 112 +++++++++++++---------
src/backend/parser/gram.y | 111 +++++++++++++--------
src/bin/pg_dump/pg_dump.c | 57 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 +++++
src/bin/psql/describe.c | 93 ++++++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 11 ++-
src/test/regress/expected/publication.out | 72 +++++++++++++-
src/test/regress/sql/publication.sql | 33 ++++++-
src/test/subscription/t/037_except.pl | 75 +++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 582 insertions(+), 158 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 9e7868487de..f55ca9ac71a 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -118,8 +118,12 @@
synchronized at any time. For more information, see
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
- be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ be explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 85cfcaddafa..360dc7eda6a 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,22 +193,26 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
- Once a table is excluded, the exclusion applies to that table
- regardless of its name or schema. Renaming the table or moving it to
- another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
- not remove the exclusion.
+ Once a table or sequence is excluded, the exclusion applies to that
+ object regardless of its name or schema. Renaming the object or moving
+ it to another schema using <command>ALTER TABLE ... SET SCHEMA</command>
+ or <command>ALTER SEQUENCE ... SET SCHEMA</command> does not remove the
+ exclusion.
</para>
<para>
For inherited tables, if <literal>ONLY</literal> is specified before the
@@ -223,9 +231,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -553,11 +562,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..f03b85fa34b 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause,
+ * while 'targetrelkind' is the relkind of the relation being added.
+ * Error if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1063,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1079,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..76cd8f58f13 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -177,11 +177,12 @@ parse_publication_options(ParseState *pstate,
/*
* Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * PublicationRelation list.
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,30 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
List *rels;
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ rels = OpenRelationList(excepttbls);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +987,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +995,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1273,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1284,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1307,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1381,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1428,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1669,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1701,16 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /* EXCEPT clause is not supported with ALTER PUBLICATION */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1733,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1852,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1870,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2009,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2054,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2072,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..4c001ef2853 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c56437d6057..d52809c26a8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,22 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ /* TODO : Add a version check for PG 20 */
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4734,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+
+ if (++n_except == 1)
+ appendPQExpBufferStr(query, " EXCEPT (");
+ else
+ appendPQExpBufferStr(query, ", ");
+ appendPQExpBuffer(query, "SEQUENCE %s", fmtQualifiedDumpable(tbinfo));
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..c2c1d515674 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3283,6 +3283,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4082,6 +4102,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index af3935b0078..cd30f36f9b2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,12 +1882,32 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
- "\n AND pg_catalog.pg_relation_is_publishable('%s')"
- "\nORDER BY 1",
+ "\n AND pg_catalog.pg_relation_is_publishable('%s')",
oid);
+ /*
+ * Skip entries where this sequence appears in the publication's
+ * EXCEPT list.
+ *
+ * For a FOR ALL SEQUENCES publication, pg_publication_rel
+ * contains entries only for sequences specified in the EXCEPT
+ * clause, so there is no need to check pr.prexcept explicitly.
+ *
+ * TODO: Add a version check for PG 20.
+ */
+ if (pset.sversion >= 190000)
+ {
+ appendPQExpBuffer(&buf, "\n AND NOT EXISTS (\n"
+ " SELECT 1\n"
+ " FROM pg_catalog.pg_publication_rel pr\n"
+ " WHERE pr.prpubid = p.oid AND\n"
+ " pr.prrelid = '%s')", oid);
+ }
+
+ appendPQExpBuffer(&buf, "\nORDER BY 1");
+
result = PSQLexec(buf.data);
if (result)
{
@@ -1912,6 +1932,43 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* TODO : Add a version check for PG 20 */
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1928,6 +1985,7 @@ describeOneTableDetails(const char *schemaname,
free(footers[0]);
free(footers[1]);
+ free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6950,6 +7008,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7045,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7099,7 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,13 +7111,33 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
goto error_return;
}
}
+ if (puballsequences)
+ {
+ /* TODO : Add a version check for PG 20 */
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index de547a8cb37..12d276cbb65 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3761,6 +3761,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4133c404a6b..6870daab5fe 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4460,14 +4460,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication relation */
Node *whereClause; /* qualifications */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4476,6 +4476,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4487,7 +4488,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4504,7 +4505,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..c1a5fea2a96 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -473,6 +473,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -534,10 +535,77 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "public.regress_pub_seq0"
+ "public.regress_pub_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+ Sequence "public.regress_pub_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences3"
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_pub_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "pub_test.regress_pub_seq2"
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+ Publication regress_pub_for_allsequences_alltables1
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.testpub_tbl1"
+Except sequences:
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, pub_test.regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..0423a77d5de 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -223,6 +223,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,40 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_pub_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences3
+RESET client_min_messages;
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+RESET client_min_messages;
+
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, pub_test.regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..b559076a4d1 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,81 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '100|t', 'initial test data replicated for seq2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence lists
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub1, tap_pub2;
+ ALTER SUBSCRIPTION tap_sub REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq1 is excluded in tap_pub1 but included in tap_pub2, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq2 is excluded in tap_pub2 but included in tap_pub1, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+$node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c5db6ca6705..b09f71e790a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2472,8 +2472,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-24 06:04 ` Peter Smith <[email protected]>
1 sibling, 0 replies; 99+ messages in thread
From: Peter Smith @ 2026-06-24 06:04 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Shlok.
Some review comments for patches of v12
//////////
Patch v12-0001
//////////
======
src/backend/commands/publicationcmds.c
ObjectsInPublicationToOids:
1.
/*
* Convert the PublicationObjSpecType list into schema oid list and
* PublicationRelation list.
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
List **rels, List **excepttbls, List **exceptseqs,
List **schemas)
I think this function comment can be made clearer. IIUC, there are 4
possible outputs from this function but the comment currently only
mentions 2. Also, it might be better to rearrange to make the
description use the same order as the output parameters.
SUGGESTION
Convert the PublicationObjSpecType list into PublicationRelation lists
(`rels`, `excepttbls`, `exceptseqs`) and a schema oid list
(`schemas`).
~~~
CreatePublication:
2.
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
List *rels;
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ rels = OpenRelationList(excepttbls);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
Here the 1st `rels` declaration does the assignment at the same time,
but the 2nd one does not. These code fragment are almost identical, so
let's make the declarations/assignments consistent.
~~~
3.
/* EXCEPT clause is not supported with ALTER PUBLICATION */
Assert(exceptseqs == NIL);
The comment is a bit misleading because:
- AFAIK this is only temporary for patch 0001.
- It is only EXCEPT (SEQUENCE ...) that is not supported yet; not
every EXCEPT clause.
SUGGESTION
TODO - The EXCEPT (SEQUENCE ...) clause is not yet supported with
ALTER PUBLICATION
======
src/backend/parser/gram.y
4.
static void
preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
bool *all_tables, bool *all_sequences,
core_yyscan_t yyscanner)
{
if (!all_objects_list)
return;
*all_tables = false;
*all_sequences = false;
Shouldn't those assignments precede the "if (!all_objects_list)",
otherwise if the booleans are not set before the check you are relying
too much that the caller's makeNode() had set them already false on
entry.
======
src/bin/pg_dump/pg_dump.c
5.
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell;
cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+
+ if (++n_except == 1)
+ appendPQExpBufferStr(query, " EXCEPT (");
+ else
+ appendPQExpBufferStr(query, ", ");
+ appendPQExpBuffer(query, "SEQUENCE %s", fmtQualifiedDumpable(tbinfo));
+ }
Unlike tables which might have qualifiers 'ONLY' and '*', there are no
additional qualifier needed for sequences, so perhaps it is better to
just generate one excluded list:
e.g.
EXCEPT (SEQUENCE a, b, c, d, e);
instead of
EXCEPT (SEQUENCE a, SEQUENCE b, SEQUENCE c, SEQUENCE d, SEQUENCE e);
~
SUGGESTION:
seqname = fmtQualifiedDumpable(tbinfo);
if (++n_except == 1)
appendPQExpBufferStr(query, " EXCEPT (SEQUENCE %s", seqname);
else
appendPQExpBufferStr(query, ", %s", seqname);
======
src/bin/psql/describe.c
6.
+ if (pset.sversion >= 190000)
+ {
+ appendPQExpBuffer(&buf, "\n AND NOT EXISTS (\n"
+ " SELECT 1\n"
+ " FROM pg_catalog.pg_publication_rel pr\n"
+ " WHERE pr.prpubid = p.oid AND\n"
+ " pr.prrelid = '%s')", oid);
IMO the first line should be split to make it more readable. Also the
other parts of this SQL statement put the \n at the beginning, so
maybe it is better to do same here too.
SUGGESTION
appendPQExpBuffer(&buf,
"\n AND NOT EXISTS ("
"\n SELECT 1"
"\n FROM pg_catalog.pg_publication_rel pr"
"\n WHERE pr.prpubid = p.oid AND"
"\n pr.prrelid = '%s')", oid);
//////////
Patch v12-0002.
//////////
======
src/backend/commands/publicationcmds.c
AlterPublicationRelations:
1.
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ SEQUENCES mode, relations are tracked as
+ * exclusions (EXCEPT clause). Fetch the current excluded
+ * relations so they can be reconciled with the specified EXCEPT
+ * list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
One place refers to "FOR ALL TABLES/ SEQUENCES" and another says "FOR
ALL TABLES/ FOR ALL SEQUENCES". Should use consistent terminology. I
preferred the 2nd variant.
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-24 09:51 ` shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
1 sibling, 1 reply; 99+ messages in thread
From: shveta malik @ 2026-06-24 09:51 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: Peter Smith <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>; shveta malik <[email protected]>
On Tue, Jun 23, 2026 at 3:03 PM Shlok Kyal <[email protected]> wrote:
>
>
> I have addressed the comments and attached the updated v12 patch
>
Thanks Shlok. I have resumed review of this thread. A few comment for 001:
1)
Can we make 'opt_sequence' (SEQUENCE | EMPTY) similar to opt_table and
then use it here. It will be similar to pub_except_tbl_list and easier
to understand.
2)
getPublications:
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
Can we do this:
if (relkind == RELKIND_SEQUENCE)
...
else if (relkind == RELKIND_RELATION ||
relkind == RELKIND_PARTITIONED_TABLE)
...
else
Assert(false);
3)
describeOneTableDetails:
+ * Skip entries where this sequence appears in the publication's
+ * EXCEPT list.
+ *
+ * For a FOR ALL SEQUENCES publication, pg_publication_rel
+ * contains entries only for sequences specified in the EXCEPT
+ * clause, so there is no need to check pr.prexcept explicitly.
My personal preference will be to have pr.prexcept check in the query
and get rid of this comment.
4)
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
Is this intentional? We are using only 3 of these though.
thanks
Shveta
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
@ 2026-06-25 08:47 ` Shlok Kyal <[email protected]>
2026-06-25 09:16 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
0 siblings, 2 replies; 99+ messages in thread
From: Shlok Kyal @ 2026-06-25 08:47 UTC (permalink / raw)
To: shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, 24 Jun 2026 at 15:21, shveta malik <[email protected]> wrote:
>
> On Tue, Jun 23, 2026 at 3:03 PM Shlok Kyal <[email protected]> wrote:
> >
> >
> > I have addressed the comments and attached the updated v12 patch
> >
>
> Thanks Shlok. I have resumed review of this thread. A few comment for 001:
>
>
> 1)
>
> Can we make 'opt_sequence' (SEQUENCE | EMPTY) similar to opt_table and
> then use it here. It will be similar to pub_except_tbl_list and easier
> to understand.
>
The compiler cannot compile if I use a syntax similar to 'pub_except_tbl_list'.
I tried making it like:
pub_except_seq_list: PublicationExceptSeqSpec
{ $$ = list_make1($1); }
| pub_except_seq_list ','
opt_sequence PublicationExceptSeqSpec
{ $$ = lappend($1, $4); } ;
Getting error as:
gram.y: error: shift/reduce conflicts: 1 found, 0 expected
gram.y: warning: shift/reduce conflict on token SEQUENCE
[-Wcounterexamples] First example: pub_except_seq_list • SEQUENCE
PublicationExceptSeqSpec Shift derivation pub_except_seq_list ↳ 1513:
pub_except_seq_list opt_sequence PublicationExceptSeqSpec ↳ 1855: •
SEQUENCE Second example: pub_except_seq_list • SEQUENCE Reduce
derivation pub_except_seq_list ↳ 1513: pub_except_seq_list
opt_sequence SEQUENCE ↳ 1856: ε •
This syntax is not able to compile because it is getting confused:
For example: ".... EXCEPT (SEQUENCE s1, SEQUENCE ... )"
For the second 'SEQUENCE', it's unclear whether we should treat
'SEQUENCE' as a sequence_name or a keyword.
To avoid the error, I wrote it the way in the patch.
'opt_table' does not face the same issue because 'TABLE' is a reserved
keyword, but 'SEQUENCE' is not.
> 2)
> getPublications:
> + if (relkind == RELKIND_SEQUENCE)
> + simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
> + else
> + simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
>
> Can we do this:
> if (relkind == RELKIND_SEQUENCE)
> ...
> else if (relkind == RELKIND_RELATION ||
> relkind == RELKIND_PARTITIONED_TABLE)
> ...
> else
> Assert(false);
>
>
> 3)
> describeOneTableDetails:
> + * Skip entries where this sequence appears in the publication's
> + * EXCEPT list.
> + *
> + * For a FOR ALL SEQUENCES publication, pg_publication_rel
> + * contains entries only for sequences specified in the EXCEPT
> + * clause, so there is no need to check pr.prexcept explicitly.
>
> My personal preference will be to have pr.prexcept check in the query
> and get rid of this comment.
>
> 4)
> - char *footers[3] = {NULL, NULL, NULL};
> + char *footers[4] = {NULL, NULL, NULL, NULL};
>
> Is this intentional? We are using only 3 of these though.
Yes, this is intentional. The last element of the footers array must
always be NULL because printQuery() treats it as a NULL-terminated
array:
/* set footers */
if (opt->footers)
{
char **footer;
for (footer = opt->footers; *footer; footer++)
printTableAddFooter(&cont, *footer);
}
The loop terminates only when it encounters a NULL entry, so the last
array element must remain NULL.
This is also consistent with the existing code. For example, in describe.c:
if (tableinfo.relkind == RELKIND_PROPGRAPH)
{
printQueryOpt popt = pset.popt;
char *footers[3] = {NULL, NULL, NULL};
only footers[0] and footers[1] are populated, while footers[2] serve
as the required terminating NULL.
I have addressed the remaining comments and attached the updated v13 patches
I have also addressed Peter's comments in [1].
[1]: https://www.postgresql.org/message-id/CAHut%2BPvMXOR73%2BWdvSWd3B8j6jDQQXRtO_bKEudi76n30GAApQ%40mail...
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v13-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (61.9K, ../../CANhcyEVzkYgMu0ah7kUmPgk6n14xsFZL8XvhiJXNsCW9oRjdGg@mail.gmail.com/2-v13-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From 4492573b6a07b549e1e6f5fde1f279518a7f0dba Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v13 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 8 +-
doc/src/sgml/ref/create_publication.sgml | 46 ++++++---
src/backend/catalog/pg_publication.c | 70 ++++++++-----
src/backend/commands/publicationcmds.c | 118 +++++++++++++---------
src/backend/parser/gram.y | 117 +++++++++++++--------
src/bin/pg_dump/pg_dump.c | 60 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 ++++
src/bin/psql/describe.c | 89 ++++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 11 +-
src/test/regress/expected/publication.out | 72 ++++++++++++-
src/test/regress/sql/publication.sql | 33 +++++-
src/test/subscription/t/037_except.pl | 75 ++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 588 insertions(+), 163 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 9e7868487de..f55ca9ac71a 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -118,8 +118,12 @@
synchronized at any time. For more information, see
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
- be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ be explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 85cfcaddafa..360dc7eda6a 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,22 +193,26 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
- Once a table is excluded, the exclusion applies to that table
- regardless of its name or schema. Renaming the table or moving it to
- another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
- not remove the exclusion.
+ Once a table or sequence is excluded, the exclusion applies to that
+ object regardless of its name or schema. Renaming the object or moving
+ it to another schema using <command>ALTER TABLE ... SET SCHEMA</command>
+ or <command>ALTER SEQUENCE ... SET SCHEMA</command> does not remove the
+ exclusion.
</para>
<para>
For inherited tables, if <literal>ONLY</literal> is specified before the
@@ -223,9 +231,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -553,11 +562,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..f03b85fa34b 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause,
+ * while 'targetrelkind' is the relkind of the relation being added.
+ * Error if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1063,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1079,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..bd171f9e48e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -176,12 +176,13 @@ parse_publication_options(ParseState *pstate,
}
/*
- * Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * Convert the PublicationObjSpecType list into PublicationRelation lists
+ * (`rels`, `excepttbls`, `exceptseqs`) and a schema oid list (`schemas`).
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,29 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
- List *rels;
+ List *rels = OpenRelationList(excepttbls);
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +986,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +994,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1272,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1283,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1306,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1380,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1427,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1668,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1700,19 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /*
+ * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
+ * PUBLICATION.
+ */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1735,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1854,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1872,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2011,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2056,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2074,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..2b34f01a301 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20747,12 +20775,12 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
bool *all_tables, bool *all_sequences,
core_yyscan_t yyscanner)
{
- if (!all_objects_list)
- return;
-
*all_tables = false;
*all_sequences = false;
+ if (!all_objects_list)
+ return;
+
foreach_ptr(PublicationAllObjSpec, obj, all_objects_list)
{
if (obj->pubobjtype == PUBLICATION_ALL_TABLES)
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c56437d6057..9669671be14 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,25 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ /* TODO : Add a version check for PG 20 */
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ else
+ Assert(false);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4737,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+ const char *seqname = fmtQualifiedDumpable(tbinfo);
+
+ if (++n_except == 1)
+ appendPQExpBuffer(query, " EXCEPT (SEQUENCE %s", seqname);
+ else
+ appendPQExpBuffer(query, ", %s", seqname);
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..c2c1d515674 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3283,6 +3283,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4082,6 +4102,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index af3935b0078..0a06496af8e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,12 +1882,28 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
- "\n AND pg_catalog.pg_relation_is_publishable('%s')"
- "\nORDER BY 1",
+ "\n AND pg_catalog.pg_relation_is_publishable('%s')",
oid);
+ /*
+ * Skip entries where this sequence appears in the publication's
+ * EXCEPT list.
+ */
+ if (pset.sversion >= 190000)
+ {
+ appendPQExpBuffer(&buf,
+ "\n AND NOT EXISTS ("
+ "\n SELECT 1"
+ "\n FROM pg_catalog.pg_publication_rel pr"
+ "\n WHERE pr.prpubid = p.oid AND"
+ "\n pr.prrelid = '%s' AND pr.prexcept)",
+ oid);
+ }
+
+ appendPQExpBuffer(&buf, "\nORDER BY 1");
+
result = PSQLexec(buf.data);
if (result)
{
@@ -1912,6 +1928,43 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* TODO : Add a version check for PG 20 */
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1928,6 +1981,7 @@ describeOneTableDetails(const char *schemaname,
free(footers[0]);
free(footers[1]);
+ free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6950,6 +7004,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7041,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7095,7 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,13 +7107,33 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
goto error_return;
}
}
+ if (puballsequences)
+ {
+ /* TODO : Add a version check for PG 20 */
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 46b9add0604..620be8318bf 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3762,6 +3762,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4133c404a6b..6870daab5fe 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4460,14 +4460,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication relation */
Node *whereClause; /* qualifications */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4476,6 +4476,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4487,7 +4488,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4504,7 +4505,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..c1a5fea2a96 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -473,6 +473,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -534,10 +535,77 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "public.regress_pub_seq0"
+ "public.regress_pub_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+ Sequence "public.regress_pub_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences3"
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_pub_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "pub_test.regress_pub_seq2"
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+ Publication regress_pub_for_allsequences_alltables1
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.testpub_tbl1"
+Except sequences:
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, pub_test.regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..0423a77d5de 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -223,6 +223,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,40 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+-- Test ALL SEQUENCES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_pub_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences3
+RESET client_min_messages;
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+RESET client_min_messages;
+
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, pub_test.regress_pub_seq2;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..b559076a4d1 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,81 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '100|t', 'initial test data replicated for seq2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence lists
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub1, tap_pub2;
+ ALTER SUBSCRIPTION tap_sub REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq1 is excluded in tap_pub1 but included in tap_pub2, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq2 is excluded in tap_pub2 but included in tap_pub1, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+$node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c5db6ca6705..b09f71e790a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2472,8 +2472,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
[application/octet-stream] v13-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (27.8K, ../../CANhcyEVzkYgMu0ah7kUmPgk6n14xsFZL8XvhiJXNsCW9oRjdGg@mail.gmail.com/3-v13-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From 49c33b8961aaf5cda4b993b730e5680c763efa58 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Thu, 25 Jun 2026 13:37:02 +0530
Subject: [PATCH v13 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 51 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 268 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 16 ++
src/test/regress/sql/publication.sql | 6 +
7 files changed, 245 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..d82ff3079db 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -73,9 +77,9 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
The third variant either modifies the included tables/schemas
- or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
- <literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ or marks the publication as <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL SEQUENCES</literal>, optionally using
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -273,10 +281,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
</programlisting></para>
<para>
- Reset the publication to be a <literal>FOR ALL TABLES</literal> publication
- with no excluded tables:
+ Reset the publication to be <literal>FOR ALL TABLES</literal> with no
+ exclusions:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index f03b85fa34b..34a8ce4d728 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -954,6 +954,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -973,8 +975,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION && relkind == RELKIND_RELATION) ||
+ (pubrelkind == RELKIND_RELATION && relkind == RELKIND_PARTITIONED_TABLE) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -990,15 +999,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,7 +1016,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1014,7 +1025,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1068,7 +1079,7 @@ GetAllTablesPublications(void)
* it excludes sequences specified in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1077,18 +1088,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index bd171f9e48e..95160c8d846 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1253,26 +1253,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1286,29 +1391,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ FOR ALL SEQUENCES mode, relations are
+ * tracked as exclusions (EXCEPT clause). Fetch the current
+ * excluded relations so they can be reconciled with the specified
+ * EXCEPT list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1321,118 +1430,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1708,11 +1724,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /*
- * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
- * PUBLICATION.
- */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1736,8 +1750,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2092,10 +2106,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 620be8318bf..b9d89778a45 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index c1a5fea2a96..206aeb181c6 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -575,16 +575,32 @@ Except sequences:
"pub_test.regress_pub_seq2"
"public.regress_pub_seq0"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_pub_seq0"
+
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 0423a77d5de..d72fc40d404 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -266,14 +266,20 @@ CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUEN
-- another schema.
ALTER SEQUENCE regress_pub_seq2 SET SCHEMA pub_test;
\dRp+ regress_pub_forallsequences3
+
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
RESET client_min_messages;
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-25 09:16 ` shveta malik <[email protected]>
1 sibling, 0 replies; 99+ messages in thread
From: shveta malik @ 2026-06-25 09:16 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: Peter Smith <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>; shveta malik <[email protected]>
On Thu, Jun 25, 2026 at 2:17 PM Shlok Kyal <[email protected]> wrote:
>
> On Wed, 24 Jun 2026 at 15:21, shveta malik <[email protected]> wrote:
> >
> > On Tue, Jun 23, 2026 at 3:03 PM Shlok Kyal <[email protected]> wrote:
> > >
> > >
> > > I have addressed the comments and attached the updated v12 patch
> > >
> >
> > Thanks Shlok. I have resumed review of this thread. A few comment for 001:
> >
> >
> > 1)
> >
> > Can we make 'opt_sequence' (SEQUENCE | EMPTY) similar to opt_table and
> > then use it here. It will be similar to pub_except_tbl_list and easier
> > to understand.
> >
> The compiler cannot compile if I use a syntax similar to 'pub_except_tbl_list'.
> I tried making it like:
> pub_except_seq_list: PublicationExceptSeqSpec
>
> { $$ = list_make1($1); }
> | pub_except_seq_list ','
> opt_sequence PublicationExceptSeqSpec
>
> { $$ = lappend($1, $4); } ;
> Getting error as:
> gram.y: error: shift/reduce conflicts: 1 found, 0 expected
> gram.y: warning: shift/reduce conflict on token SEQUENCE
> [-Wcounterexamples] First example: pub_except_seq_list • SEQUENCE
> PublicationExceptSeqSpec Shift derivation pub_except_seq_list ↳ 1513:
> pub_except_seq_list opt_sequence PublicationExceptSeqSpec ↳ 1855: •
> SEQUENCE Second example: pub_except_seq_list • SEQUENCE Reduce
> derivation pub_except_seq_list ↳ 1513: pub_except_seq_list
> opt_sequence SEQUENCE ↳ 1856: ε •
>
> This syntax is not able to compile because it is getting confused:
> For example: ".... EXCEPT (SEQUENCE s1, SEQUENCE ... )"
> For the second 'SEQUENCE', it's unclear whether we should treat
> 'SEQUENCE' as a sequence_name or a keyword.
>
> To avoid the error, I wrote it the way in the patch.
> 'opt_table' does not face the same issue because 'TABLE' is a reserved
> keyword, but 'SEQUENCE' is not.
>
Okay, thanks for clarifying. I am surprised that SEQUENCE is not a
reserved keyword.
postgres=# create sequence sequence;
CREATE SEQUENCE
Anyway, currnet implementation is fine then.
> >
> > 4)
> > - char *footers[3] = {NULL, NULL, NULL};
> > + char *footers[4] = {NULL, NULL, NULL, NULL};
> >
> > Is this intentional? We are using only 3 of these though.
>
> Yes, this is intentional. The last element of the footers array must
> always be NULL because printQuery() treats it as a NULL-terminated
> array:
> /* set footers */
> if (opt->footers)
> {
> char **footer;
>
> for (footer = opt->footers; *footer; footer++)
> printTableAddFooter(&cont, *footer);
> }
>
> The loop terminates only when it encounters a NULL entry, so the last
> array element must remain NULL.
> This is also consistent with the existing code. For example, in describe.c:
> if (tableinfo.relkind == RELKIND_PROPGRAPH)
> {
> printQueryOpt popt = pset.popt;
> char *footers[3] = {NULL, NULL, NULL};
>
> only footers[0] and footers[1] are populated, while footers[2] serve
> as the required terminating NULL.
>
Okay, thanks for the details.
thanks
Shveta
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-25 23:53 ` Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
1 sibling, 1 reply; 99+ messages in thread
From: Peter Smith @ 2026-06-25 23:53 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
//////
Patch v13-0001
//////
======
src/bin/pg_dump/t/002_pg_dump.pl
1.
Now dump.c can generate a single SEQUENCE exclusion list with multiple
items, but it doesn't seem tested because the 002_pg_dump test file
remained unchanged in v13.
I think you should enhance one of your test cases to exclude multiple sequences.
//////
Patch v13-0002
//////
No comments.
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-06-26 08:53 ` shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: shveta malik @ 2026-06-26 08:53 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>; Peter Smith <[email protected]>; shveta malik <[email protected]>
Please find a few trivial comments on v13-001:
1)
check_publication_add_relation:
+ * 'pubrelkind' is the relkind accepted by the publication clause,
+ * while 'targetrelkind' is the relkind of the relation being added.
pubrelkind is the argument while targetrelkind is not. We generally
explain arguements here.
Can we rephrase to:
* 'pubrelkind' is the relkind accepted by the publication clause.
* The relkind of relation in given 'pri' is checked for compatibility
* against it. Error is emitted if they are not compatible.
2)
describePublications:
}
- else
+ if (puballtables)
{
Now since else is converted to independent 'if' block we can add a
blank line before it for better readability.
Same for this:
}
+ if (puballsequences)
+ {
3)
We have each test header like below starting from begining of file:
---------------------------------------------
-- Tests for inherited tables, and
-- EXCEPT clause tests for inherited tables
---------------------------------------------
---------------------------------------------
-- EXCEPT clause tests for partitioned tables
---------------------------------------------
So we can convert ours too in above format so that it is more visible:
+-- Test ALL SEQUENCES with EXCEPT clause
4)
We can add these tests to sql file:
a) SEQ keyword equivalent test to below table one:
-- fail - first table in the EXCEPT list should use TABLE keyword
CREATE PUBLICATION testpub_foralltables_excepttable2 FOR ALL TABLES
EXCEPT (testpub_tbl1, testpub_tbl2);
b) Try to add either of temporay or unlogged seq to except list.
thanks
Shveta
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
@ 2026-06-26 12:38 ` Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-29 10:32 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
0 siblings, 2 replies; 99+ messages in thread
From: Shlok Kyal @ 2026-06-26 12:38 UTC (permalink / raw)
To: shveta malik <[email protected]>; +Cc: vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>; Peter Smith <[email protected]>
On Fri, 26 Jun 2026 at 14:23, shveta malik <[email protected]> wrote:
>
> Please find a few trivial comments on v13-001:
>
> 1)
>
> check_publication_add_relation:
>
> + * 'pubrelkind' is the relkind accepted by the publication clause,
> + * while 'targetrelkind' is the relkind of the relation being added.
>
> pubrelkind is the argument while targetrelkind is not. We generally
> explain arguements here.
>
> Can we rephrase to:
> * 'pubrelkind' is the relkind accepted by the publication clause.
> * The relkind of relation in given 'pri' is checked for compatibility
> * against it. Error is emitted if they are not compatible.
>
> 2)
> describePublications:
>
> }
> - else
> + if (puballtables)
> {
>
> Now since else is converted to independent 'if' block we can add a
> blank line before it for better readability.
>
> Same for this:
>
> }
> + if (puballsequences)
> + {
>
>
> 3)
>
> We have each test header like below starting from begining of file:
>
> ---------------------------------------------
> -- Tests for inherited tables, and
> -- EXCEPT clause tests for inherited tables
> ---------------------------------------------
>
> ---------------------------------------------
> -- EXCEPT clause tests for partitioned tables
> ---------------------------------------------
>
> So we can convert ours too in above format so that it is more visible:
> +-- Test ALL SEQUENCES with EXCEPT clause
>
> 4)
> We can add these tests to sql file:
>
> a) SEQ keyword equivalent test to below table one:
>
> -- fail - first table in the EXCEPT list should use TABLE keyword
> CREATE PUBLICATION testpub_foralltables_excepttable2 FOR ALL TABLES
> EXCEPT (testpub_tbl1, testpub_tbl2);
>
> b) Try to add either of temporay or unlogged seq to except list.
>
I have addressed all the comments. I have also addressed the comments
by Peter in [1].
Please find the updated v14 patch.
[1]: https://www.postgresql.org/message-id/CAHut%2BPsgCQa2hcT8TNqejV3y4U5ouj%3DZCOLMxgVGvBrUEkLaKg%40mail...
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v14-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (64.0K, ../../CANhcyEW_4=OECRiHStsCJUJ_Ng-V=EJ+Oaeji4uk3s4yazqUBg@mail.gmail.com/2-v14-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From 43d6db22eae33eea94f6f00e1abd558fc09e503e Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v14 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 8 +-
doc/src/sgml/ref/create_publication.sgml | 46 ++++++---
src/backend/catalog/pg_publication.c | 70 ++++++++-----
src/backend/commands/publicationcmds.c | 118 +++++++++++++---------
src/backend/parser/gram.y | 117 +++++++++++++--------
src/bin/pg_dump/pg_dump.c | 60 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 ++++
src/bin/psql/describe.c | 91 +++++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 11 +-
src/test/regress/expected/publication.out | 89 +++++++++++++++-
src/test/regress/sql/publication.sql | 46 ++++++++-
src/test/subscription/t/037_except.pl | 75 ++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 620 insertions(+), 163 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 5befefd9c5a..01c8f084db9 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -118,8 +118,12 @@
synchronized at any time. For more information, see
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
- be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ be explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 85cfcaddafa..360dc7eda6a 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,22 +193,26 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
- Once a table is excluded, the exclusion applies to that table
- regardless of its name or schema. Renaming the table or moving it to
- another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
- not remove the exclusion.
+ Once a table or sequence is excluded, the exclusion applies to that
+ object regardless of its name or schema. Renaming the object or moving
+ it to another schema using <command>ALTER TABLE ... SET SCHEMA</command>
+ or <command>ALTER SEQUENCE ... SET SCHEMA</command> does not remove the
+ exclusion.
</para>
<para>
For inherited tables, if <literal>ONLY</literal> is specified before the
@@ -223,9 +231,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -553,11 +562,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..ee80a1c0457 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause. The relkind
+ * of the relation in 'pri' is checked for compatibility against it. Error is
+ * raised if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1063,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1079,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..bd171f9e48e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -176,12 +176,13 @@ parse_publication_options(ParseState *pstate,
}
/*
- * Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * Convert the PublicationObjSpecType list into PublicationRelation lists
+ * (`rels`, `excepttbls`, `exceptseqs`) and a schema oid list (`schemas`).
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,29 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
- List *rels;
+ List *rels = OpenRelationList(excepttbls);
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +986,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +994,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1272,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1283,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1306,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1380,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1427,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1668,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1700,19 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /*
+ * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
+ * PUBLICATION.
+ */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1735,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1854,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1872,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2011,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2056,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2074,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..2b34f01a301 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20747,12 +20775,12 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
bool *all_tables, bool *all_sequences,
core_yyscan_t yyscanner)
{
- if (!all_objects_list)
- return;
-
*all_tables = false;
*all_sequences = false;
+ if (!all_objects_list)
+ return;
+
foreach_ptr(PublicationAllObjSpec, obj, all_objects_list)
{
if (obj->pubobjtype == PUBLICATION_ALL_TABLES)
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c56437d6057..9669671be14 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,25 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ /* TODO : Add a version check for PG 20 */
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ else
+ Assert(false);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4737,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+ const char *seqname = fmtQualifiedDumpable(tbinfo);
+
+ if (++n_except == 1)
+ appendPQExpBuffer(query, " EXCEPT (SEQUENCE %s", seqname);
+ else
+ appendPQExpBuffer(query, ", %s", seqname);
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..9a0673575d2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3283,6 +3283,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql => 'CREATE SEQUENCE test_except_seq;
+ CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4082,6 +4102,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index af3935b0078..1a773202ea6 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,12 +1882,28 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
- "\n AND pg_catalog.pg_relation_is_publishable('%s')"
- "\nORDER BY 1",
+ "\n AND pg_catalog.pg_relation_is_publishable('%s')",
oid);
+ /*
+ * Skip entries where this sequence appears in the publication's
+ * EXCEPT list.
+ */
+ if (pset.sversion >= 190000)
+ {
+ appendPQExpBuffer(&buf,
+ "\n AND NOT EXISTS ("
+ "\n SELECT 1"
+ "\n FROM pg_catalog.pg_publication_rel pr"
+ "\n WHERE pr.prpubid = p.oid AND"
+ "\n pr.prrelid = '%s' AND pr.prexcept)",
+ oid);
+ }
+
+ appendPQExpBuffer(&buf, "\nORDER BY 1");
+
result = PSQLexec(buf.data);
if (result)
{
@@ -1912,6 +1928,43 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* TODO : Add a version check for PG 20 */
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1928,6 +1981,7 @@ describeOneTableDetails(const char *schemaname,
free(footers[0]);
free(footers[1]);
+ free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6950,6 +7004,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7041,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7095,8 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,7 +7108,7 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
@@ -7060,6 +7116,27 @@ describePublications(const char *pattern)
}
}
+ if (puballsequences)
+ {
+ /* TODO : Add a version check for PG 20 */
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
+
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 46b9add0604..620be8318bf 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3762,6 +3762,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4133c404a6b..6870daab5fe 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4460,14 +4460,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication relation */
Node *whereClause; /* qualifications */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4476,6 +4476,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4487,7 +4488,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4504,7 +4505,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..5fc6dcd69c4 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -473,6 +473,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -534,10 +535,94 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "public.regress_pub_seq0"
+ "public.regress_pub_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+ Sequence "public.regress_pub_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences3"
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_pub_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_pub_seq1"
+ "pub_test.regress_pub_seq2"
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (regress_pub_seq0, pub_test.regress_pub_seq1);
+ERROR: syntax error at or near "regress_pub_seq0"
+LINE 1: ...gress_pub_forallsequences4 FOR ALL TABLES EXCEPT (regress_pu...
+ ^
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_pub_seq3;
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq3);
+ERROR: cannot specify "public.regress_pub_seq3" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_pub_seq4;
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq4);
+ERROR: cannot specify "pg_temp.regress_pub_seq4" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+ Publication regress_pub_for_allsequences_alltables1
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.testpub_tbl1"
+Except sequences:
+ "public.regress_pub_seq0"
+
+RESET client_min_messages;
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, pub_test.regress_pub_seq2, regress_pub_seq3, regress_pub_seq4;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..d9138eeb85a 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -223,6 +223,7 @@ DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
CREATE SEQUENCE regress_pub_seq0;
CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_pub_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,53 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, SEQUENCE regress_pub_seq2);
+\dRp+ regress_pub_forallsequences3
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_pub_seq0
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_pub_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences3
+RESET client_min_messages;
+
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (regress_pub_seq0, pub_test.regress_pub_seq1);
+
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_pub_seq3;
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq3);
+
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_pub_seq4;
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq4);
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_for_allsequences_alltables1
+RESET client_min_messages;
+
+DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1, pub_test.regress_pub_seq2, regress_pub_seq3, regress_pub_seq4;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences3;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables1;
+DROP TABLE tab_seq;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..b559076a4d1 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,81 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq1;
+ CREATE SEQUENCE seq2;
+ CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '100|t', 'initial test data replicated for seq2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence lists
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub SET PUBLICATION tap_pub1, tap_pub2;
+ ALTER SUBSCRIPTION tap_sub REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq1 is excluded in tap_pub1 but included in tap_pub2, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq2 is excluded in tap_pub2 but included in tap_pub1, so overall the
+# subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+$node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
+$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c5db6ca6705..b09f71e790a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2472,8 +2472,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
[application/octet-stream] v14-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (30.7K, ../../CANhcyEW_4=OECRiHStsCJUJ_Ng-V=EJ+Oaeji4uk3s4yazqUBg@mail.gmail.com/3-v14-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From e1f9b6846a52b7133e964f4ed5f2cee2f1628915 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Fri, 26 Jun 2026 18:04:33 +0530
Subject: [PATCH v14 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 51 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 268 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 26 +++
src/test/regress/sql/publication.sql | 9 +
7 files changed, 258 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..d82ff3079db 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -73,9 +77,9 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
The third variant either modifies the included tables/schemas
- or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
- <literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ or marks the publication as <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL SEQUENCES</literal>, optionally using
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -273,10 +281,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
</programlisting></para>
<para>
- Reset the publication to be a <literal>FOR ALL TABLES</literal> publication
- with no excluded tables:
+ Reset the publication to be <literal>FOR ALL TABLES</literal> with no
+ exclusions:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index ee80a1c0457..42e50533f7d 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -954,6 +954,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -973,8 +975,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION && relkind == RELKIND_RELATION) ||
+ (pubrelkind == RELKIND_RELATION && relkind == RELKIND_PARTITIONED_TABLE) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -990,15 +999,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,7 +1016,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1014,7 +1025,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1068,7 +1079,7 @@ GetAllTablesPublications(void)
* it excludes sequences specified in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1077,18 +1088,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index bd171f9e48e..95160c8d846 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1253,26 +1253,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1286,29 +1391,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ FOR ALL SEQUENCES mode, relations are
+ * tracked as exclusions (EXCEPT clause). Fetch the current
+ * excluded relations so they can be reconciled with the specified
+ * EXCEPT list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1321,118 +1430,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1708,11 +1724,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /*
- * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
- * PUBLICATION.
- */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1736,8 +1750,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2092,10 +2106,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 620be8318bf..b9d89778a45 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 5fc6dcd69c4..aaab820a3ed 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -577,31 +577,57 @@ Except sequences:
"pub_test.regress_pub_seq2"
"public.regress_pub_seq0"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
+ Publication regress_pub_forallsequences3
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_pub_seq0"
+
RESET client_min_messages;
-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (regress_pub_seq0, pub_test.regress_pub_seq1);
ERROR: syntax error at or near "regress_pub_seq0"
LINE 1: ...gress_pub_forallsequences4 FOR ALL TABLES EXCEPT (regress_pu...
^
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (regress_pub_seq0, pub_test.regress_pub_seq1);
+ERROR: syntax error at or near "regress_pub_seq0"
+LINE 1: ...ss_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (regress_pu...
+ ^
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_pub_seq3;
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq3);
ERROR: cannot specify "public.regress_pub_seq3" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for unlogged sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq3);
+ERROR: cannot specify "public.regress_pub_seq3" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_pub_seq4;
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq4);
ERROR: cannot specify "pg_temp.regress_pub_seq4" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for temporary sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq4);
+ERROR: cannot specify "pg_temp.regress_pub_seq4" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ERROR: cannot specify "public.regress_pub_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ERROR: cannot specify "public.tab_seq" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_for_allsequences_alltables1 FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index d9138eeb85a..655323236cb 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -268,25 +268,34 @@ CREATE PUBLICATION regress_pub_forallsequences3 FOR ALL SEQUENCES EXCEPT (SEQUEN
-- another schema.
ALTER SEQUENCE regress_pub_seq2 SET SCHEMA pub_test;
\dRp+ regress_pub_forallsequences3
+
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq0);
+\dRp+ regress_pub_forallsequences3
RESET client_min_messages;
-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (regress_pub_seq0, pub_test.regress_pub_seq1);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (regress_pub_seq0, pub_test.regress_pub_seq1);
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_pub_seq3;
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq3);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq3);
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_pub_seq4;
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq4);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE regress_pub_seq4);
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT (TABLE regress_pub_seq0);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL TABLES EXCEPT (TABLE regress_pub_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab_seq(a int);
CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES EXCEPT (SEQUENCE tab_seq);
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-29 00:31 ` Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
1 sibling, 1 reply; 99+ messages in thread
From: Peter Smith @ 2026-06-29 00:31 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Shlok.
I only had one general comment about v14. IMO, it would be better to
try to make the regression test object names more meaningful where
possible (though sometimes it won't be). The xxx1, xxx2, and xxx3
become harder to read as more gets added.
e.g. `regress_pub_seq3` -- What does 'pub' mean here? This is an
unlogged sequence you want to exclude.
e.g. `regress_pub_forallsequences4` -- Doesn't mean much.
e.g. `tab_seq` -- This is meant to be a plain table; Not a table
pretending to be a seq.
Below is an example of some modifications, but there are many more.
Consider checking all the names to see if they can be improved
(sometimes you may be stuck having to accommodate existing names).
(same comment applies to both patches).
~~~
BEFORE
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_pub_seq3;
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
EXCEPT (SEQUENCE regress_pub_seq3);
+
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_pub_seq4;
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
EXCEPT (SEQUENCE regress_pub_seq4);
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT
(TABLE regress_pub_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab_seq(a int);
+CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
EXCEPT (SEQUENCE tab_seq);
+
SUGGESTION
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
(SEQUENCE regress_seq_unlogged);
+
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
(SEQUENCE regress_seq_temp);
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT
(TABLE regress_seq1);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab1(a int);
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
(SEQUENCE tab1);
+
======
Kind Regards,
Peter Smith.
Fujitsu Australia.
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-06-30 06:43 ` Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-06-30 23:49 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
0 siblings, 2 replies; 99+ messages in thread
From: Shlok Kyal @ 2026-06-30 06:43 UTC (permalink / raw)
To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, 29 Jun 2026 at 06:02, Peter Smith <[email protected]> wrote:
>
> Hi Shlok.
>
> I only had one general comment about v14. IMO, it would be better to
> try to make the regression test object names more meaningful where
> possible (though sometimes it won't be). The xxx1, xxx2, and xxx3
> become harder to read as more gets added.
>
> e.g. `regress_pub_seq3` -- What does 'pub' mean here? This is an
> unlogged sequence you want to exclude.
> e.g. `regress_pub_forallsequences4` -- Doesn't mean much.
> e.g. `tab_seq` -- This is meant to be a plain table; Not a table
> pretending to be a seq.
>
> Below is an example of some modifications, but there are many more.
> Consider checking all the names to see if they can be improved
> (sometimes you may be stuck having to accommodate existing names).
> (same comment applies to both patches).
>
> ~~~
>
> BEFORE
> +-- fail - unlogged sequence is specified in EXCEPT sequence list
> +CREATE UNLOGGED SEQUENCE regress_pub_seq3;
> +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
> EXCEPT (SEQUENCE regress_pub_seq3);
> +
> +-- fail - temporary sequence is specified in EXCEPT sequence list
> +CREATE TEMPORARY SEQUENCE regress_pub_seq4;
> +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
> EXCEPT (SEQUENCE regress_pub_seq4);
> +
> +-- fail - sequence object is specified in EXCEPT table list
> +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT
> (TABLE regress_pub_seq0);
> +
> +-- fail - table object is specified in EXCEPT sequence list
> +CREATE TABLE tab_seq(a int);
> +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
> EXCEPT (SEQUENCE tab_seq);
> +
>
>
> SUGGESTION
> +-- fail - unlogged sequence is specified in EXCEPT sequence list
> +CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
> +CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
> (SEQUENCE regress_seq_unlogged);
> +
> +-- fail - temporary sequence is specified in EXCEPT sequence list
> +CREATE TEMPORARY SEQUENCE regress_seq_temp;
> +CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
> (SEQUENCE regress_seq_temp);
> +
> +-- fail - sequence object is specified in EXCEPT table list
> +CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT
> (TABLE regress_seq1);
> +
> +-- fail - table object is specified in EXCEPT sequence list
> +CREATE TABLE tab1(a int);
> +CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
> (SEQUENCE tab1);
> +
>
I agree with your suggestions, I have made the changes.
I have also made changes for other objects as well in publication.sql
and 037_except.pl files.
I also agree with the suggestions by Shveta in [1] and made the
required changes.
Please find the updated v15 patch attached.
[1]: https://www.postgresql.org/message-id/[email protected]...
Thanks,
Shlok Kyal
Attachments:
[application/x-patch] v15-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (31.5K, ../../CANhcyEV201=vsz8aoOpSgYWEbG4WuxtL1P=a8X9N+48ZBtBfNQ@mail.gmail.com/2-v15-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From a4afd89d40fb316e0cdbf78475cde56a6bb0e7a3 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 30 Jun 2026 11:17:12 +0530
Subject: [PATCH v15 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 51 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 268 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 35 +++
src/test/regress/sql/publication.sql | 13 ++
7 files changed, 271 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..d82ff3079db 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -73,9 +77,9 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
The third variant either modifies the included tables/schemas
- or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
- <literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ or marks the publication as <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL SEQUENCES</literal>, optionally using
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -273,10 +281,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
</programlisting></para>
<para>
- Reset the publication to be a <literal>FOR ALL TABLES</literal> publication
- with no excluded tables:
+ Reset the publication to be <literal>FOR ALL TABLES</literal> with no
+ exclusions:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index ee80a1c0457..4dd074e96b9 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -954,6 +954,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -973,8 +975,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -990,15 +999,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,7 +1016,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1014,7 +1025,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1068,7 +1079,7 @@ GetAllTablesPublications(void)
* it excludes sequences specified in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1077,18 +1088,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index bd171f9e48e..95160c8d846 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1253,26 +1253,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1286,29 +1391,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ FOR ALL SEQUENCES mode, relations are
+ * tracked as exclusions (EXCEPT clause). Fetch the current
+ * excluded relations so they can be reconciled with the specified
+ * EXCEPT list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1321,118 +1430,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1708,11 +1724,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /*
- * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
- * PUBLICATION.
- */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1736,8 +1750,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2092,10 +2106,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 620be8318bf..b9d89778a45 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 0148718b3b3..d6db76fc3c3 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -577,31 +577,66 @@ Except sequences:
"pub_test.regress_seq2"
"public.regress_seq0"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_seq0"
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+(1 row)
+
RESET client_min_messages;
-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
ERROR: syntax error at or near "regress_seq0"
LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
^
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ..._forallsequences_except SET ALL SEQUENCES EXCEPT (regress_se...
+ ^
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for unlogged sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for temporary sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab1(a int);
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE tab1);
ERROR: cannot specify "public.tab1" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE tab1);
+ERROR: cannot specify "public.tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 47e7111860e..b49089c06e4 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -268,25 +268,38 @@ CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (
-- another schema.
ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
\dRp+ regress_pub_forallsequences_except
+
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
RESET client_min_messages;
-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE TABLE tab1(a int);
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE tab1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE tab1);
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
SET client_min_messages = 'ERROR';
--
2.34.1
[application/x-patch] v15-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (66.6K, ../../CANhcyEV201=vsz8aoOpSgYWEbG4WuxtL1P=a8X9N+48ZBtBfNQ@mail.gmail.com/3-v15-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From a53ee1354dcdaa445239c3d4de1749a7e53a39db Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v15 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 8 +-
doc/src/sgml/ref/create_publication.sgml | 46 ++++++---
src/backend/catalog/pg_publication.c | 70 ++++++++-----
src/backend/commands/publicationcmds.c | 118 +++++++++++++---------
src/backend/parser/gram.y | 117 +++++++++++++--------
src/bin/pg_dump/pg_dump.c | 60 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 ++++
src/bin/psql/describe.c | 91 +++++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 15 +--
src/test/regress/expected/publication.out | 101 ++++++++++++++++--
src/test/regress/sql/publication.sql | 54 +++++++++-
src/test/subscription/t/037_except.pl | 78 ++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 635 insertions(+), 175 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 5befefd9c5a..01c8f084db9 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -118,8 +118,12 @@
synchronized at any time. For more information, see
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
- be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ be explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 85cfcaddafa..360dc7eda6a 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,22 +193,26 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
- Once a table is excluded, the exclusion applies to that table
- regardless of its name or schema. Renaming the table or moving it to
- another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
- not remove the exclusion.
+ Once a table or sequence is excluded, the exclusion applies to that
+ object regardless of its name or schema. Renaming the object or moving
+ it to another schema using <command>ALTER TABLE ... SET SCHEMA</command>
+ or <command>ALTER SEQUENCE ... SET SCHEMA</command> does not remove the
+ exclusion.
</para>
<para>
For inherited tables, if <literal>ONLY</literal> is specified before the
@@ -223,9 +231,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -553,11 +562,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..ee80a1c0457 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause. The relkind
+ * of the relation in 'pri' is checked for compatibility against it. Error is
+ * raised if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1063,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1079,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..bd171f9e48e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -176,12 +176,13 @@ parse_publication_options(ParseState *pstate,
}
/*
- * Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * Convert the PublicationObjSpecType list into PublicationRelation lists
+ * (`rels`, `excepttbls`, `exceptseqs`) and a schema oid list (`schemas`).
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,29 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
- List *rels;
+ List *rels = OpenRelationList(excepttbls);
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +986,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +994,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1272,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1283,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1306,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1380,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1427,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1668,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1700,19 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /*
+ * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
+ * PUBLICATION.
+ */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1735,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1854,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1872,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2011,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2056,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2074,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..2b34f01a301 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20747,12 +20775,12 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
bool *all_tables, bool *all_sequences,
core_yyscan_t yyscanner)
{
- if (!all_objects_list)
- return;
-
*all_tables = false;
*all_sequences = false;
+ if (!all_objects_list)
+ return;
+
foreach_ptr(PublicationAllObjSpec, obj, all_objects_list)
{
if (obj->pubobjtype == PUBLICATION_ALL_TABLES)
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c56437d6057..9669671be14 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,25 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ /* TODO : Add a version check for PG 20 */
+ if (relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ else
+ Assert(false);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4737,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+ const char *seqname = fmtQualifiedDumpable(tbinfo);
+
+ if (++n_except == 1)
+ appendPQExpBuffer(query, " EXCEPT (SEQUENCE %s", seqname);
+ else
+ appendPQExpBuffer(query, ", %s", seqname);
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..9a0673575d2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3283,6 +3283,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql => 'CREATE SEQUENCE test_except_seq;
+ CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4082,6 +4102,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index af3935b0078..1a773202ea6 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,12 +1882,28 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
- "\n AND pg_catalog.pg_relation_is_publishable('%s')"
- "\nORDER BY 1",
+ "\n AND pg_catalog.pg_relation_is_publishable('%s')",
oid);
+ /*
+ * Skip entries where this sequence appears in the publication's
+ * EXCEPT list.
+ */
+ if (pset.sversion >= 190000)
+ {
+ appendPQExpBuffer(&buf,
+ "\n AND NOT EXISTS ("
+ "\n SELECT 1"
+ "\n FROM pg_catalog.pg_publication_rel pr"
+ "\n WHERE pr.prpubid = p.oid AND"
+ "\n pr.prrelid = '%s' AND pr.prexcept)",
+ oid);
+ }
+
+ appendPQExpBuffer(&buf, "\nORDER BY 1");
+
result = PSQLexec(buf.data);
if (result)
{
@@ -1912,6 +1928,43 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* TODO : Add a version check for PG 20 */
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 190000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1928,6 +1981,7 @@ describeOneTableDetails(const char *schemaname,
free(footers[0]);
free(footers[1]);
+ free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6950,6 +7004,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7041,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7095,8 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,7 +7108,7 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
@@ -7060,6 +7116,27 @@ describePublications(const char *pattern)
}
}
+ if (puballsequences)
+ {
+ /* TODO : Add a version check for PG 20 */
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
+
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 46b9add0604..620be8318bf 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3762,6 +3762,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4133c404a6b..9311cdf7def 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4460,14 +4460,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
- RangeVar *relation; /* publication relation */
- Node *whereClause; /* qualifications */
+ RangeVar *relation; /* publication table/sequence */
+ Node *whereClause; /* qualifications for publication table */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4476,6 +4476,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4487,7 +4488,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4504,7 +4505,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..0148718b3b3 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -471,8 +471,9 @@ RESET client_min_messages;
DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -483,8 +484,8 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_pub_forallsequences1 | f | t
(1 row)
-\d+ regress_pub_seq0
- Sequence "public.regress_pub_seq0"
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -502,8 +503,8 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
- Sequence "pub_test.regress_pub_seq1"
+\d+ pub_test.regress_seq1
+ Sequence "pub_test.regress_seq1"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -534,10 +535,94 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "public.regress_seq0"
+ "public.regress_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences_except"
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "pub_test.regress_seq2"
+ "public.regress_seq0"
+
+RESET client_min_messages;
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
+ ^
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab1(a int);
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE tab1);
+ERROR: cannot specify "public.tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+ Publication regress_pub_for_allsequences_alltables_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.testpub_tbl1"
+Except sequences:
+ "public.regress_seq0"
+
+RESET client_min_messages;
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..47e7111860e 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -221,8 +221,9 @@ DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -230,7 +231,7 @@ CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_forallsequences1';
-\d+ regress_pub_seq0
+\d+ regress_seq0
\dRp+ regress_pub_forallsequences1
SET client_min_messages = 'ERROR';
@@ -238,7 +239,7 @@ CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
+\d+ pub_test.regress_seq1
--- Specifying both ALL TABLES and ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,53 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_seq0
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+RESET client_min_messages;
+
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab1(a int);
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE tab1);
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+RESET client_min_messages;
+
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..05d29e4aec8 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,84 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence lists
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub_all_seq_except SET PUBLICATION tap_pub_all_seq_except1, tap_pub_all_seq_except2;
+ ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq_excluded_in_pub1 is excluded in tap_pub_all_seq_except1 but included in
+# tap_pub_all_seq_except2, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq_excluded_in_pub2 is excluded in tap_pub_all_seq_except2 but included in
+# tap_pub_all_seq_except1, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+$node_subscriber->safe_psql('postgres',
+ 'DROP SUBSCRIPTION tap_sub_all_seq_except');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except1');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3a2720fb5f9..fb2aa9b0f8c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2472,8 +2472,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-30 07:46 ` Ashutosh Sharma <[email protected]>
2026-07-01 08:30 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
1 sibling, 1 reply; 99+ messages in thread
From: Ashutosh Sharma @ 2026-06-30 07:46 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: Peter Smith <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On Tue, Jun 30, 2026 at 12:14 PM Shlok Kyal <[email protected]> wrote:
>
> On Mon, 29 Jun 2026 at 06:02, Peter Smith <[email protected]> wrote:
> >
> > Hi Shlok.
> >
> > I only had one general comment about v14. IMO, it would be better to
> > try to make the regression test object names more meaningful where
> > possible (though sometimes it won't be). The xxx1, xxx2, and xxx3
> > become harder to read as more gets added.
> >
> > e.g. `regress_pub_seq3` -- What does 'pub' mean here? This is an
> > unlogged sequence you want to exclude.
> > e.g. `regress_pub_forallsequences4` -- Doesn't mean much.
> > e.g. `tab_seq` -- This is meant to be a plain table; Not a table
> > pretending to be a seq.
> >
> > Below is an example of some modifications, but there are many more.
> > Consider checking all the names to see if they can be improved
> > (sometimes you may be stuck having to accommodate existing names).
> > (same comment applies to both patches).
> >
> > ~~~
> >
> > BEFORE
> > +-- fail - unlogged sequence is specified in EXCEPT sequence list
> > +CREATE UNLOGGED SEQUENCE regress_pub_seq3;
> > +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
> > EXCEPT (SEQUENCE regress_pub_seq3);
> > +
> > +-- fail - temporary sequence is specified in EXCEPT sequence list
> > +CREATE TEMPORARY SEQUENCE regress_pub_seq4;
> > +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
> > EXCEPT (SEQUENCE regress_pub_seq4);
> > +
> > +-- fail - sequence object is specified in EXCEPT table list
> > +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT
> > (TABLE regress_pub_seq0);
> > +
> > +-- fail - table object is specified in EXCEPT sequence list
> > +CREATE TABLE tab_seq(a int);
> > +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
> > EXCEPT (SEQUENCE tab_seq);
> > +
> >
> >
> > SUGGESTION
> > +-- fail - unlogged sequence is specified in EXCEPT sequence list
> > +CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
> > +CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
> > (SEQUENCE regress_seq_unlogged);
> > +
> > +-- fail - temporary sequence is specified in EXCEPT sequence list
> > +CREATE TEMPORARY SEQUENCE regress_seq_temp;
> > +CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
> > (SEQUENCE regress_seq_temp);
> > +
> > +-- fail - sequence object is specified in EXCEPT table list
> > +CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT
> > (TABLE regress_seq1);
> > +
> > +-- fail - table object is specified in EXCEPT sequence list
> > +CREATE TABLE tab1(a int);
> > +CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
> > (SEQUENCE tab1);
> > +
> >
> I agree with your suggestions, I have made the changes.
> I have also made changes for other objects as well in publication.sql
> and 037_except.pl files.
>
> I also agree with the suggestions by Shveta in [1] and made the
> required changes.
>
> Please find the updated v15 patch attached.
>
+ /* TODO : Add a version check for PG 20 */
+ if (pset.sversion >= 190000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
I've few questions related to above change:
1) Should the second printfPQExpBuffer() be appendPQExpBuffer()? As
written, it overwrites the query comment added just above, so psql -E
will not show "Get sequences excluded by this publication".
2) The above query may work on PG 19, but since the feature is going
to be part of PG20, why do you want this query to be part of PG19?
--
Do we also need to include a tap test for "ALTER PUBLICATION ... SET
ALL SEQUENCES EXCEPT (...)" followed by "ALTER SUBSCRIPTION ...
REFRESH SEQUENCES", and then sequence replication behavior is
verified?
--
With Regards,
Ashutosh Sharma.
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
@ 2026-07-01 08:30 ` Shlok Kyal <[email protected]>
2026-07-01 23:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Shlok Kyal @ 2026-07-01 08:30 UTC (permalink / raw)
To: Ashutosh Sharma <[email protected]>; +Cc: Peter Smith <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, 30 Jun 2026 at 13:17, Ashutosh Sharma <[email protected]> wrote:
>
> Hi,
>
> On Tue, Jun 30, 2026 at 12:14 PM Shlok Kyal <[email protected]> wrote:
> >
> > On Mon, 29 Jun 2026 at 06:02, Peter Smith <[email protected]> wrote:
> > >
> > > Hi Shlok.
> > >
> > > I only had one general comment about v14. IMO, it would be better to
> > > try to make the regression test object names more meaningful where
> > > possible (though sometimes it won't be). The xxx1, xxx2, and xxx3
> > > become harder to read as more gets added.
> > >
> > > e.g. `regress_pub_seq3` -- What does 'pub' mean here? This is an
> > > unlogged sequence you want to exclude.
> > > e.g. `regress_pub_forallsequences4` -- Doesn't mean much.
> > > e.g. `tab_seq` -- This is meant to be a plain table; Not a table
> > > pretending to be a seq.
> > >
> > > Below is an example of some modifications, but there are many more.
> > > Consider checking all the names to see if they can be improved
> > > (sometimes you may be stuck having to accommodate existing names).
> > > (same comment applies to both patches).
> > >
> > > ~~~
> > >
> > > BEFORE
> > > +-- fail - unlogged sequence is specified in EXCEPT sequence list
> > > +CREATE UNLOGGED SEQUENCE regress_pub_seq3;
> > > +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
> > > EXCEPT (SEQUENCE regress_pub_seq3);
> > > +
> > > +-- fail - temporary sequence is specified in EXCEPT sequence list
> > > +CREATE TEMPORARY SEQUENCE regress_pub_seq4;
> > > +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
> > > EXCEPT (SEQUENCE regress_pub_seq4);
> > > +
> > > +-- fail - sequence object is specified in EXCEPT table list
> > > +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL TABLES EXCEPT
> > > (TABLE regress_pub_seq0);
> > > +
> > > +-- fail - table object is specified in EXCEPT sequence list
> > > +CREATE TABLE tab_seq(a int);
> > > +CREATE PUBLICATION regress_pub_forallsequences4 FOR ALL SEQUENCES
> > > EXCEPT (SEQUENCE tab_seq);
> > > +
> > >
> > >
> > > SUGGESTION
> > > +-- fail - unlogged sequence is specified in EXCEPT sequence list
> > > +CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
> > > +CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
> > > (SEQUENCE regress_seq_unlogged);
> > > +
> > > +-- fail - temporary sequence is specified in EXCEPT sequence list
> > > +CREATE TEMPORARY SEQUENCE regress_seq_temp;
> > > +CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
> > > (SEQUENCE regress_seq_temp);
> > > +
> > > +-- fail - sequence object is specified in EXCEPT table list
> > > +CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT
> > > (TABLE regress_seq1);
> > > +
> > > +-- fail - table object is specified in EXCEPT sequence list
> > > +CREATE TABLE tab1(a int);
> > > +CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
> > > (SEQUENCE tab1);
> > > +
> > >
> > I agree with your suggestions, I have made the changes.
> > I have also made changes for other objects as well in publication.sql
> > and 037_except.pl files.
> >
> > I also agree with the suggestions by Shveta in [1] and made the
> > required changes.
> >
> > Please find the updated v15 patch attached.
> >
>
> + /* TODO : Add a version check for PG 20 */
> + if (pset.sversion >= 190000)
> + {
> + /* Get sequences in the EXCEPT clause for this publication */
> + printfPQExpBuffer(&buf, "/* %s */\n",
> + _("Get sequences excluded by this publication"));
> + printfPQExpBuffer(&buf,
> + "SELECT n.nspname || '.' || c.relname\n"
> + "FROM pg_catalog.pg_class c\n"
> + " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
> + " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
> + "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
> + "ORDER BY 1", pubid);
> + if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
> + true, &cont))
> + goto error_return;
> + }
> + }
>
> I've few questions related to above change:
>
> 1) Should the second printfPQExpBuffer() be appendPQExpBuffer()? As
> written, it overwrites the query comment added just above, so psql -E
> will not show "Get sequences excluded by this publication".
>
I agree, the second printfPQExpBuffer() should be appendPQExpBuffer().
Made the change.
> 2) The above query may work on PG 19, but since the feature is going
> to be part of PG20, why do you want this query to be part of PG19?
>
I added the checks for PG19, as the major version on master was still
pointing to PG19.
But now I noticed that the major version is now bumped to PG20, I have
modified the checks
> --
>
> Do we also need to include a tap test for "ALTER PUBLICATION ... SET
> ALL SEQUENCES EXCEPT (...)" followed by "ALTER SUBSCRIPTION ...
> REFRESH SEQUENCES", and then sequence replication behavior is
> verified?
>
Yes, I agree
I have made the changes and attached the updated v16 patches. I have
also addressed Peter's comment in [1].
[1]: https://www.postgresql.org/message-id/[email protected]...
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v16-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (66.5K, ../../CANhcyEWtAUnHF-3MKTSmH+j-A9sQSfuTdcCA9K285bL1ALmnyw@mail.gmail.com/2-v16-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From a5f0fbf18904738b3adbc200b236219afd751a14 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v16 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 8 +-
doc/src/sgml/ref/create_publication.sgml | 46 ++++++---
src/backend/catalog/pg_publication.c | 70 ++++++++-----
src/backend/commands/publicationcmds.c | 118 +++++++++++++---------
src/backend/parser/gram.y | 117 +++++++++++++--------
src/bin/pg_dump/pg_dump.c | 60 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 ++++
src/bin/psql/describe.c | 89 ++++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 15 +--
src/test/regress/expected/publication.out | 99 ++++++++++++++++--
src/test/regress/sql/publication.sql | 52 +++++++++-
src/test/subscription/t/037_except.pl | 79 +++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 630 insertions(+), 175 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 5befefd9c5a..01c8f084db9 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -118,8 +118,12 @@
synchronized at any time. For more information, see
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
- be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ be explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 85cfcaddafa..360dc7eda6a 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,22 +193,26 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
- Once a table is excluded, the exclusion applies to that table
- regardless of its name or schema. Renaming the table or moving it to
- another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
- not remove the exclusion.
+ Once a table or sequence is excluded, the exclusion applies to that
+ object regardless of its name or schema. Renaming the object or moving
+ it to another schema using <command>ALTER TABLE ... SET SCHEMA</command>
+ or <command>ALTER SEQUENCE ... SET SCHEMA</command> does not remove the
+ exclusion.
</para>
<para>
For inherited tables, if <literal>ONLY</literal> is specified before the
@@ -223,9 +231,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -553,11 +562,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5c457d9aca8..ee80a1c0457 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause. The relkind
+ * of the relation in 'pri' is checked for compatibility against it. Error is
+ * raised if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -97,11 +115,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -521,7 +543,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -559,7 +582,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -979,15 +1002,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1038,8 +1063,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1053,11 +1079,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..bd171f9e48e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -176,12 +176,13 @@ parse_publication_options(ParseState *pstate,
}
/*
- * Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * Convert the PublicationObjSpecType list into PublicationRelation lists
+ * (`rels`, `excepttbls`, `exceptseqs`) and a schema oid list (`schemas`).
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,29 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
- List *rels;
+ List *rels = OpenRelationList(excepttbls);
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +986,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +994,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1272,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1283,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1306,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1380,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1427,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1668,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1700,19 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /*
+ * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
+ * PUBLICATION.
+ */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1735,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1854,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1872,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2011,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2056,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2074,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..2b34f01a301 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20747,12 +20775,12 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
bool *all_tables, bool *all_sequences,
core_yyscan_t yyscanner)
{
- if (!all_objects_list)
- return;
-
*all_tables = false;
*all_sequences = false;
+ if (!all_objects_list)
+ return;
+
foreach_ptr(PublicationAllObjSpec, obj, all_objects_list)
{
if (obj->pubobjtype == PUBLICATION_ALL_TABLES)
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c56437d6057..80df95fc394 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,18 +4617,22 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
@@ -4642,9 +4646,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4659,25 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ if (fout->remoteVersion >= 200000 &&
+ relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ else
+ Assert(false);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4737,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+ const char *seqname = fmtQualifiedDumpable(tbinfo);
+
+ if (++n_except == 1)
+ appendPQExpBuffer(query, " EXCEPT (SEQUENCE %s", seqname);
+ else
+ appendPQExpBuffer(query, ", %s", seqname);
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..9a0673575d2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3283,6 +3283,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql => 'CREATE SEQUENCE test_except_seq;
+ CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4082,6 +4102,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index af3935b0078..9c77f552b1d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,12 +1882,28 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
- "\n AND pg_catalog.pg_relation_is_publishable('%s')"
- "\nORDER BY 1",
+ "\n AND pg_catalog.pg_relation_is_publishable('%s')",
oid);
+ /*
+ * Skip entries where this sequence appears in the publication's
+ * EXCEPT list.
+ */
+ if (pset.sversion >= 200000)
+ {
+ appendPQExpBuffer(&buf,
+ "\n AND NOT EXISTS ("
+ "\n SELECT 1"
+ "\n FROM pg_catalog.pg_publication_rel pr"
+ "\n WHERE pr.prpubid = p.oid AND"
+ "\n pr.prrelid = '%s' AND pr.prexcept)",
+ oid);
+ }
+
+ appendPQExpBuffer(&buf, "\nORDER BY 1");
+
result = PSQLexec(buf.data);
if (result)
{
@@ -1912,6 +1928,42 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 200000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1928,6 +1980,7 @@ describeOneTableDetails(const char *schemaname,
free(footers[0]);
free(footers[1]);
+ free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6950,6 +7003,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7040,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7094,8 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,7 +7107,7 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
@@ -7060,6 +7115,26 @@ describePublications(const char *pattern)
}
}
+ if (puballsequences)
+ {
+ if (pset.sversion >= 200000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ appendPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
+
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 46b9add0604..620be8318bf 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3762,6 +3762,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4133c404a6b..9311cdf7def 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4460,14 +4460,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
- RangeVar *relation; /* publication relation */
- Node *whereClause; /* qualifications */
+ RangeVar *relation; /* publication table/sequence */
+ Node *whereClause; /* qualifications for publication table */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4476,6 +4476,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4487,7 +4488,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4504,7 +4505,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..6442b87d038 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -471,8 +471,9 @@ RESET client_min_messages;
DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -483,8 +484,8 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_pub_forallsequences1 | f | t
(1 row)
-\d+ regress_pub_seq0
- Sequence "public.regress_pub_seq0"
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -502,8 +503,8 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
- Sequence "pub_test.regress_pub_seq1"
+\d+ pub_test.regress_seq1
+ Sequence "pub_test.regress_seq1"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -534,10 +535,92 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "public.regress_seq0"
+ "public.regress_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences_except"
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "pub_test.regress_seq2"
+ "public.regress_seq0"
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+CREATE TABLE regress_tab1(a int);
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+ Publication regress_pub_for_allsequences_alltables_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.regress_tab1"
+Except sequences:
+ "public.regress_seq0"
+
+RESET client_min_messages;
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
+ ^
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE regress_tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..11c20e65143 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -221,8 +221,9 @@ DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -230,7 +231,7 @@ CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_forallsequences1';
-\d+ regress_pub_seq0
+\d+ regress_seq0
\dRp+ regress_pub_forallsequences1
SET client_min_messages = 'ERROR';
@@ -238,7 +239,7 @@ CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
+\d+ pub_test.regress_seq1
--- Specifying both ALL TABLES and ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,51 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_seq0
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+CREATE TABLE regress_tab1(a int);
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+RESET client_min_messages;
+
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE regress_tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..7c4eb589cd4 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,85 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence lists
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub_all_seq_except SET PUBLICATION tap_pub_all_seq_except1, tap_pub_all_seq_except2;
+ ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq_excluded_in_pub1 is excluded in tap_pub_all_seq_except1 but included in
+# tap_pub_all_seq_except2, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq_excluded_in_pub2 is excluded in tap_pub_all_seq_except2 but included in
+# tap_pub_all_seq_except1, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# Cleanup
+$node_subscriber->safe_psql('postgres',
+ 'DROP SUBSCRIPTION tap_sub_all_seq_except');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except1');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3a2720fb5f9..fb2aa9b0f8c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2472,8 +2472,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
[application/octet-stream] v16-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (33.8K, ../../CANhcyEWtAUnHF-3MKTSmH+j-A9sQSfuTdcCA9K285bL1ALmnyw@mail.gmail.com/3-v16-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From 8e133017e324ad72eb0084fe29af3434c7a4aae4 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Wed, 1 Jul 2026 12:12:47 +0530
Subject: [PATCH v16 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 51 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 268 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 35 +++
src/test/regress/sql/publication.sql | 13 ++
src/test/subscription/t/037_except.pl | 14 ++
8 files changed, 285 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..d82ff3079db 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -73,9 +77,9 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
The third variant either modifies the included tables/schemas
- or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
- <literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ or marks the publication as <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL SEQUENCES</literal>, optionally using
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -273,10 +281,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
</programlisting></para>
<para>
- Reset the publication to be a <literal>FOR ALL TABLES</literal> publication
- with no excluded tables:
+ Reset the publication to be <literal>FOR ALL TABLES</literal> with no
+ exclusions:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index ee80a1c0457..4dd074e96b9 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -946,7 +946,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -954,6 +954,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -973,8 +975,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -990,15 +999,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1006,7 +1016,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1014,7 +1025,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1068,7 +1079,7 @@ GetAllTablesPublications(void)
* it excludes sequences specified in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1077,18 +1088,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index bd171f9e48e..95160c8d846 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1253,26 +1253,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1286,29 +1391,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ FOR ALL SEQUENCES mode, relations are
+ * tracked as exclusions (EXCEPT clause). Fetch the current
+ * excluded relations so they can be reconciled with the specified
+ * EXCEPT list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1321,118 +1430,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1708,11 +1724,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /*
- * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
- * PUBLICATION.
- */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1736,8 +1750,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2092,10 +2106,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 620be8318bf..b9d89778a45 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 6442b87d038..4a02488bffb 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -577,6 +577,25 @@ Except sequences:
"pub_test.regress_seq2"
"public.regress_seq0"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_seq0"
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+(1 row)
+
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
CREATE TABLE regress_tab1(a int);
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
@@ -596,24 +615,40 @@ CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0,
ERROR: syntax error at or near "regress_seq0"
LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
^
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ..._forallsequences_except SET ALL SEQUENCES EXCEPT (regress_se...
+ ^
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for unlogged sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for temporary sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 11c20e65143..92ecfc83aa2 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -269,6 +269,14 @@ CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (
ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
\dRp+ regress_pub_forallsequences_except
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
CREATE TABLE regress_tab1(a int);
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
@@ -277,20 +285,25 @@ RESET client_min_messages;
-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 7c4eb589cd4..ac0e7b62e37 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -289,6 +289,7 @@ $node_publisher->safe_psql(
'postgres', qq (
CREATE TABLE seq_test (v BIGINT);
CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub1_2;
CREATE SEQUENCE seq_excluded_in_pub2;
INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
@@ -297,6 +298,7 @@ $node_publisher->safe_psql(
$node_subscriber->safe_psql(
'postgres', qq(
CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub1_2;
CREATE SEQUENCE seq_excluded_in_pub2;
CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
));
@@ -316,6 +318,18 @@ $result = $node_subscriber->safe_psql('postgres',
"SELECT last_value, is_called FROM seq_excluded_in_pub2");
is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+# Check ALTER PUBLICATION ... ALL SEQUENCES (EXCEPT SEQUENCE ...)
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ ALTER PUBLICATION tap_pub_all_seq_except1 SET ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1, seq_excluded_in_pub1_2);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1_2') FROM generate_series(1,100);)
+);
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1_2");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
+
# ============================================
# Test when a subscription is subscribing to multiple publications
# ============================================
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-01 08:30 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-07-01 23:22 ` Peter Smith <[email protected]>
2026-07-02 06:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Peter Smith @ 2026-07-01 23:22 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: Ashutosh Sharma <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Only trivial comments for v16*
//////
v16-0001
//////
======
src/bin/pg_dump/pg_dump.c
getPublictions:
1.
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
Perhaps this comment should say that EXCEPT (TABLE ...) is introduced
in PG19, and EXCEPT (SEQUENCE ...) is introduced in PG20. That way
there are no surprises about the different version checks that follow.
======
src/test/subscription/t/037_except.pl
2.
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
typo: plural. Should be "sequences in the EXCEPT list are excluded"
//////
v16-0002
//////
======
src/test/subscription/t/037_except.pl
1.
+is($result, '1|f', 'sequences in EXCEPT list is excluded');
typo: plural. Should be "sequences in the EXCEPT list are excluded"
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-01 08:30 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-01 23:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-07-02 06:22 ` Shlok Kyal <[email protected]>
2026-07-03 05:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Shlok Kyal @ 2026-07-02 06:22 UTC (permalink / raw)
To: Peter Smith <[email protected]>; +Cc: Ashutosh Sharma <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, 2 Jul 2026 at 04:53, Peter Smith <[email protected]> wrote:
>
> Only trivial comments for v16*
>
> //////
> v16-0001
> //////
>
> ======
> src/bin/pg_dump/pg_dump.c
>
> getPublictions:
>
> 1.
> /*
> - * Get the list of tables for publications specified in the EXCEPT
> - * TABLE clause.
> + * Get the list of tables and sequences for publications specified in
> + * the EXCEPT clause.
> *
>
> Perhaps this comment should say that EXCEPT (TABLE ...) is introduced
> in PG19, and EXCEPT (SEQUENCE ...) is introduced in PG20. That way
> there are no surprises about the different version checks that follow.
>
> ======
> src/test/subscription/t/037_except.pl
>
> 2.
> +# Check the initial data on subscriber
> +$result = $node_subscriber->safe_psql('postgres',
> + "SELECT last_value, is_called FROM seq_excluded_in_pub1");
> +is($result, '1|f', 'sequences in EXCEPT list is excluded');
>
> typo: plural. Should be "sequences in the EXCEPT list are excluded"
>
> //////
> v16-0002
> //////
>
> ======
> src/test/subscription/t/037_except.pl
>
> 1.
> +is($result, '1|f', 'sequences in EXCEPT list is excluded');
>
> typo: plural. Should be "sequences in the EXCEPT list are excluded"
>
Thanks Peter for the review. I have addressed the comments.
Also the patch needed a rebase after the recent commits.
Please find the updated v17 patch attached.
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v17-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (66.8K, ../../CANhcyEXxi-OnjVuKs47tT7kfQRbPESZs_z4q9jZFK15yQ0z+LA@mail.gmail.com/2-v17-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From 20ece7b47da51c8a2ebf3d529d80453ed8185cae Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v17 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 8 +-
doc/src/sgml/ref/create_publication.sgml | 46 ++++++---
src/backend/catalog/pg_publication.c | 70 ++++++++-----
src/backend/commands/publicationcmds.c | 118 +++++++++++++---------
src/backend/parser/gram.y | 117 +++++++++++++--------
src/bin/pg_dump/pg_dump.c | 65 +++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 ++++
src/bin/psql/describe.c | 89 ++++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 15 +--
src/test/regress/expected/publication.out | 99 ++++++++++++++++--
src/test/regress/sql/publication.sql | 52 +++++++++-
src/test/subscription/t/037_except.pl | 79 +++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 635 insertions(+), 175 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index f21239af6b8..a2961bd3c41 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -118,8 +118,12 @@
synchronized at any time. For more information, see
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
- be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ be explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 85cfcaddafa..360dc7eda6a 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,22 +193,26 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
- Once a table is excluded, the exclusion applies to that table
- regardless of its name or schema. Renaming the table or moving it to
- another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
- not remove the exclusion.
+ Once a table or sequence is excluded, the exclusion applies to that
+ object regardless of its name or schema. Renaming the object or moving
+ it to another schema using <command>ALTER TABLE ... SET SCHEMA</command>
+ or <command>ALTER SEQUENCE ... SET SCHEMA</command> does not remove the
+ exclusion.
</para>
<para>
For inherited tables, if <literal>ONLY</literal> is specified before the
@@ -223,9 +231,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -553,11 +562,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 1ec94c851b2..5eb4de744de 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause. The relkind
+ * of the relation in 'pri' is checked for compatibility against it. Error is
+ * raised if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -104,11 +122,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -531,7 +553,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -569,7 +592,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -989,15 +1012,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1048,8 +1073,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1063,11 +1089,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..bd171f9e48e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -176,12 +176,13 @@ parse_publication_options(ParseState *pstate,
}
/*
- * Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * Convert the PublicationObjSpecType list into PublicationRelation lists
+ * (`rels`, `excepttbls`, `exceptseqs`) and a schema oid list (`schemas`).
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,29 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
- List *rels;
+ List *rels = OpenRelationList(excepttbls);
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +986,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +994,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1272,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1283,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1306,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1380,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1427,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1668,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1700,19 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /*
+ * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
+ * PUBLICATION.
+ */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1735,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1854,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1872,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2011,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2056,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2074,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..2b34f01a301 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+pub_except_tbl_list: PublicationExceptTblSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
+ { $$ = list_make1($1); }
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20747,12 +20775,12 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
bool *all_tables, bool *all_sequences,
core_yyscan_t yyscanner)
{
- if (!all_objects_list)
- return;
-
*all_tables = false;
*all_sequences = false;
+ if (!all_objects_list)
+ return;
+
foreach_ptr(PublicationAllObjSpec, obj, all_objects_list)
{
if (obj->pubobjtype == PUBLICATION_ALL_TABLES)
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 25f66b56d40..ab9e19f042a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4617,23 +4617,31 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
* collect this information, which is then emitted by
* dumpPublication().
+ *
+ * Note: EXCEPT (TABLE ...) was introduced in PG19, whereas EXCEPT
+ * (SEQUENCE ...) was introduced in PG20. Hence, the version checks
+ * below differ.
*/
if (fout->remoteVersion >= 190000)
{
@@ -4642,9 +4650,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4654,14 +4663,26 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ /* EXCEPT (SEQUENCE ...) is supported from PG20 onwards. */
+ if (fout->remoteVersion >= 200000 &&
+ relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ else
+ Assert(false);
+ }
}
PQclear(res_tbls);
@@ -4721,12 +4742,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+ const char *seqname = fmtQualifiedDumpable(tbinfo);
+
+ if (++n_except == 1)
+ appendPQExpBuffer(query, " EXCEPT (SEQUENCE %s", seqname);
+ else
+ appendPQExpBuffer(query, ", %s", seqname);
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..9a0673575d2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3283,6 +3283,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql => 'CREATE SEQUENCE test_except_seq;
+ CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4082,6 +4102,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a1a440a6469..bb5e29911d5 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1778,7 +1778,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
if (pset.sversion >= 100000)
@@ -1882,12 +1882,28 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
- "\n AND pg_catalog.pg_relation_is_publishable('%s')"
- "\nORDER BY 1",
+ "\n AND pg_catalog.pg_relation_is_publishable('%s')",
oid);
+ /*
+ * Skip entries where this sequence appears in the publication's
+ * EXCEPT list.
+ */
+ if (pset.sversion >= 200000)
+ {
+ appendPQExpBuffer(&buf,
+ "\n AND NOT EXISTS ("
+ "\n SELECT 1"
+ "\n FROM pg_catalog.pg_publication_rel pr"
+ "\n WHERE pr.prpubid = p.oid AND"
+ "\n pr.prrelid = '%s' AND pr.prexcept)",
+ oid);
+ }
+
+ appendPQExpBuffer(&buf, "\nORDER BY 1");
+
result = PSQLexec(buf.data);
if (result)
{
@@ -1912,6 +1928,42 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 200000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1928,6 +1980,7 @@ describeOneTableDetails(const char *schemaname,
pg_free(footers[0]);
pg_free(footers[1]);
+ pg_free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6950,6 +7003,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6986,7 +7040,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -7040,7 +7094,8 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -7052,7 +7107,7 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
@@ -7060,6 +7115,26 @@ describePublications(const char *pattern)
}
}
+ if (puballsequences)
+ {
+ if (pset.sversion >= 200000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ appendPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
+
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index f4c839c3953..5d2c23b2d9d 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3762,6 +3762,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e03556399ab..246bd44e6ac 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4461,14 +4461,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
- RangeVar *relation; /* publication relation */
- Node *whereClause; /* qualifications */
+ RangeVar *relation; /* publication table/sequence */
+ Node *whereClause; /* qualifications for publication table */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4477,6 +4477,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4488,7 +4489,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4505,7 +4506,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..6442b87d038 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -471,8 +471,9 @@ RESET client_min_messages;
DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -483,8 +484,8 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_pub_forallsequences1 | f | t
(1 row)
-\d+ regress_pub_seq0
- Sequence "public.regress_pub_seq0"
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -502,8 +503,8 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
- Sequence "pub_test.regress_pub_seq1"
+\d+ pub_test.regress_seq1
+ Sequence "pub_test.regress_seq1"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -534,10 +535,92 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "public.regress_seq0"
+ "public.regress_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences_except"
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "pub_test.regress_seq2"
+ "public.regress_seq0"
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+CREATE TABLE regress_tab1(a int);
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+ Publication regress_pub_for_allsequences_alltables_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.regress_tab1"
+Except sequences:
+ "public.regress_seq0"
+
+RESET client_min_messages;
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
+ ^
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE regress_tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..11c20e65143 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -221,8 +221,9 @@ DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -230,7 +231,7 @@ CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_forallsequences1';
-\d+ regress_pub_seq0
+\d+ regress_seq0
\dRp+ regress_pub_forallsequences1
SET client_min_messages = 'ERROR';
@@ -238,7 +239,7 @@ CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
+\d+ pub_test.regress_seq1
--- Specifying both ALL TABLES and ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,51 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_seq0
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+CREATE TABLE regress_tab1(a int);
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+RESET client_min_messages;
+
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE regress_tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..c35ef9403ee 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,85 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '1|f', 'sequences in the EXCEPT list are excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence lists
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub_all_seq_except SET PUBLICATION tap_pub_all_seq_except1, tap_pub_all_seq_except2;
+ ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq_excluded_in_pub1 is excluded in tap_pub_all_seq_except1 but included in
+# tap_pub_all_seq_except2, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq_excluded_in_pub2 is excluded in tap_pub_all_seq_except2 but included in
+# tap_pub_all_seq_except1, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# Cleanup
+$node_subscriber->safe_psql('postgres',
+ 'DROP SUBSCRIPTION tap_sub_all_seq_except');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except1');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 117e7379f10..08aee4b4933 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2475,8 +2475,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
[application/octet-stream] v17-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (33.9K, ../../CANhcyEXxi-OnjVuKs47tT7kfQRbPESZs_z4q9jZFK15yQ0z+LA@mail.gmail.com/3-v17-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From 3a2732f1b44b2c4dc93de4053858c8b471bc5c03 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Wed, 1 Jul 2026 12:12:47 +0530
Subject: [PATCH v17 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 51 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 268 ++++++++++++----------
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 35 +++
src/test/regress/sql/publication.sql | 13 ++
src/test/subscription/t/037_except.pl | 14 ++
8 files changed, 285 insertions(+), 152 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..d82ff3079db 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -73,9 +77,9 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
The third variant either modifies the included tables/schemas
- or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
- <literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ or marks the publication as <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL SEQUENCES</literal>, optionally using
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -273,10 +281,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
</programlisting></para>
<para>
- Reset the publication to be a <literal>FOR ALL TABLES</literal> publication
- with no excluded tables:
+ Reset the publication to be <literal>FOR ALL TABLES</literal> with no
+ exclusions:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5eb4de744de..32850002e58 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -956,7 +956,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -964,6 +964,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -983,8 +985,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -1000,15 +1009,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1016,7 +1026,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1024,7 +1035,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1078,7 +1089,7 @@ GetAllTablesPublications(void)
* it excludes sequences specified in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1087,18 +1098,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index bd171f9e48e..95160c8d846 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1253,26 +1253,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1286,29 +1391,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ FOR ALL SEQUENCES mode, relations are
+ * tracked as exclusions (EXCEPT clause). Fetch the current
+ * excluded relations so they can be reconciled with the specified
+ * EXCEPT list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1321,118 +1430,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1708,11 +1724,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /*
- * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
- * PUBLICATION.
- */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1736,8 +1750,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2092,10 +2106,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 5d2c23b2d9d..7194fa53de1 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2351,6 +2351,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 6442b87d038..4a02488bffb 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -577,6 +577,25 @@ Except sequences:
"pub_test.regress_seq2"
"public.regress_seq0"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_seq0"
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+(1 row)
+
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
CREATE TABLE regress_tab1(a int);
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
@@ -596,24 +615,40 @@ CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0,
ERROR: syntax error at or near "regress_seq0"
LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
^
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ..._forallsequences_except SET ALL SEQUENCES EXCEPT (regress_se...
+ ^
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for unlogged sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for temporary sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 11c20e65143..92ecfc83aa2 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -269,6 +269,14 @@ CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (
ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
\dRp+ regress_pub_forallsequences_except
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
CREATE TABLE regress_tab1(a int);
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
@@ -277,20 +285,25 @@ RESET client_min_messages;
-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index c35ef9403ee..d3166b213f4 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -289,6 +289,7 @@ $node_publisher->safe_psql(
'postgres', qq (
CREATE TABLE seq_test (v BIGINT);
CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub1_2;
CREATE SEQUENCE seq_excluded_in_pub2;
INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
@@ -297,6 +298,7 @@ $node_publisher->safe_psql(
$node_subscriber->safe_psql(
'postgres', qq(
CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub1_2;
CREATE SEQUENCE seq_excluded_in_pub2;
CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
));
@@ -316,6 +318,18 @@ $result = $node_subscriber->safe_psql('postgres',
"SELECT last_value, is_called FROM seq_excluded_in_pub2");
is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+# Check ALTER PUBLICATION ... ALL SEQUENCES (EXCEPT SEQUENCE ...)
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ ALTER PUBLICATION tap_pub_all_seq_except1 SET ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1, seq_excluded_in_pub1_2);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1_2') FROM generate_series(1,100);)
+);
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1_2");
+is($result, '1|f', 'sequences in the EXCEPT list are excluded');
+
# ============================================
# Test when a subscription is subscribing to multiple publications
# ============================================
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-01 08:30 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-01 23:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-07-02 06:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-07-03 05:53 ` Ashutosh Sharma <[email protected]>
2026-07-03 09:48 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Ashutosh Sharma @ 2026-07-03 05:53 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: Peter Smith <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On Thu, Jul 2, 2026 at 11:53 AM Shlok Kyal <[email protected]> wrote:
>
> Please find the updated v17 patch attached.
>
Thank you for the updated patches. I see that the following command
works with your patch:
CREATE PUBLICATION combined_seq_pub_1
FOR ALL SEQUENCES
EXCEPT (SEQUENCE ONLY s1_seq);
May I know what the purpose of ONLY is here?
AFAIU, for FOR ALL TABLES, ONLY was added to handle inherited tables.
However, sequences do not have inheritance, so I am not sure what
semantics ONLY is intended to provide for sequences. Could you please
clarify?
--
With Regards,
Ashutosh Sharma.
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-01 08:30 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-01 23:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-07-02 06:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-03 05:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
@ 2026-07-03 09:48 ` Shlok Kyal <[email protected]>
2026-07-06 06:04 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Shlok Kyal @ 2026-07-03 09:48 UTC (permalink / raw)
To: Ashutosh Sharma <[email protected]>; +Cc: Peter Smith <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, 3 Jul 2026 at 11:24, Ashutosh Sharma <[email protected]> wrote:
>
> Hi,
>
> On Thu, Jul 2, 2026 at 11:53 AM Shlok Kyal <[email protected]> wrote:
> >
> > Please find the updated v17 patch attached.
> >
>
> Thank you for the updated patches. I see that the following command
> works with your patch:
>
> CREATE PUBLICATION combined_seq_pub_1
> FOR ALL SEQUENCES
> EXCEPT (SEQUENCE ONLY s1_seq);
>
> May I know what the purpose of ONLY is here?
>
> AFAIU, for FOR ALL TABLES, ONLY was added to handle inherited tables.
> However, sequences do not have inheritance, so I am not sure what
> semantics ONLY is intended to provide for sequences. Could you please
> clarify?
It was unintentional. ONLY should not be allowed with SEQUENCE.
This happened because, in gram.y, the EXCEPT SEQUENCE production uses
relation_expr:
+PublicationExceptSeqSpec:
+ relation_expr
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
relation_expr accepts ONLY, so it inadvertently allows syntax such as
EXCEPT (SEQUENCE ONLY s1).
I think it would be better to use qualified_name instead. That's also
what CREATE SEQUENCE uses, so it would be consistent with the rest of
the grammar and would prevent ONLY from being accepted.
With this change we are now throwing syntax error:
postgres=# CREATE PUBLICATION combined_seq_pub_1
FOR ALL SEQUENCES
EXCEPT (SEQUENCE ONLY s1_seq);
ERROR: syntax error at or near "ONLY"
LINE 3: EXCEPT (SEQUENCE ONLY s1_seq);
While making the change, I also found that I missed updating the
comments in gram.y for this feature.
I have updated the same and attached the updated v18 patches.
Thanks,
Shlok Kyal
Attachments:
[application/octet-stream] v18-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (67.1K, ../../CANhcyEUmDrWf-cUe9s0OCLvAtLUSNy347nL-8FWqbax+JD4yiA@mail.gmail.com/2-v18-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From 4e8822f751e81bc0699bbf853785e9bcf7a8eef9 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v18 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 8 +-
doc/src/sgml/ref/create_publication.sgml | 46 ++++++---
src/backend/catalog/pg_publication.c | 70 ++++++++-----
src/backend/commands/publicationcmds.c | 118 +++++++++++++--------
src/backend/parser/gram.y | 119 ++++++++++++++--------
src/bin/pg_dump/pg_dump.c | 65 +++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 ++++
src/bin/psql/describe.c | 89 ++++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 15 +--
src/test/regress/expected/publication.out | 99 ++++++++++++++++--
src/test/regress/sql/publication.sql | 52 +++++++++-
src/test/subscription/t/037_except.pl | 79 ++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 636 insertions(+), 176 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index f21239af6b8..a2961bd3c41 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -118,8 +118,12 @@
synchronized at any time. For more information, see
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
- be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ be explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 85cfcaddafa..360dc7eda6a 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,22 +193,26 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
- Once a table is excluded, the exclusion applies to that table
- regardless of its name or schema. Renaming the table or moving it to
- another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
- not remove the exclusion.
+ Once a table or sequence is excluded, the exclusion applies to that
+ object regardless of its name or schema. Renaming the object or moving
+ it to another schema using <command>ALTER TABLE ... SET SCHEMA</command>
+ or <command>ALTER SEQUENCE ... SET SCHEMA</command> does not remove the
+ exclusion.
</para>
<para>
For inherited tables, if <literal>ONLY</literal> is specified before the
@@ -223,9 +231,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -553,11 +562,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 1ec94c851b2..5eb4de744de 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause. The relkind
+ * of the relation in 'pri' is checked for compatibility against it. Error is
+ * raised if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -104,11 +122,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -531,7 +553,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -569,7 +592,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -989,15 +1012,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1048,8 +1073,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1063,11 +1089,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..bd171f9e48e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -176,12 +176,13 @@ parse_publication_options(ParseState *pstate,
}
/*
- * Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * Convert the PublicationObjSpecType list into PublicationRelation lists
+ * (`rels`, `excepttbls`, `exceptseqs`) and a schema oid list (`schemas`).
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,29 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
- List *rels;
+ List *rels = OpenRelationList(excepttbls);
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +986,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +994,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1272,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1283,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1306,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1380,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1427,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1668,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1700,19 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /*
+ * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
+ * PUBLICATION.
+ */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1735,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1854,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1872,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2011,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2056,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2074,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..f0be22ed941 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11265,7 +11267,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
* pub_all_obj_type is one of:
*
* TABLES [EXCEPT (TABLE table [, ...] )]
- * SEQUENCES
+ * SEQUENCES [EXCEPT (SEQUENCE sequence [, ...] )]
*
* CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
*
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+PublicationExceptSeqSpec:
+ qualified_name
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+pub_except_tbl_list: PublicationExceptTblSpec
+ { $$ = list_make1($1); }
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20747,12 +20775,12 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
bool *all_tables, bool *all_sequences,
core_yyscan_t yyscanner)
{
- if (!all_objects_list)
- return;
-
*all_tables = false;
*all_sequences = false;
+ if (!all_objects_list)
+ return;
+
foreach_ptr(PublicationAllObjSpec, obj, all_objects_list)
{
if (obj->pubobjtype == PUBLICATION_ALL_TABLES)
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f67daf85911..7c2ba635712 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4571,23 +4571,31 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
* collect this information, which is then emitted by
* dumpPublication().
+ *
+ * Note: EXCEPT (TABLE ...) was introduced in PG19, whereas EXCEPT
+ * (SEQUENCE ...) was introduced in PG20. Hence, the version checks
+ * below differ.
*/
if (fout->remoteVersion >= 190000)
{
@@ -4596,9 +4604,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4608,14 +4617,26 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ /* EXCEPT (SEQUENCE ...) is supported from PG20 onwards. */
+ if (fout->remoteVersion >= 200000 &&
+ relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ else
+ Assert(false);
+ }
}
PQclear(res_tbls);
@@ -4675,12 +4696,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+ const char *seqname = fmtQualifiedDumpable(tbinfo);
+
+ if (++n_except == 1)
+ appendPQExpBuffer(query, " EXCEPT (SEQUENCE %s", seqname);
+ else
+ appendPQExpBuffer(query, ", %s", seqname);
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..9a0673575d2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3283,6 +3283,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql => 'CREATE SEQUENCE test_except_seq;
+ CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4082,6 +4102,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index fef86b4cca3..63cb6a394ef 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1672,7 +1672,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
appendPQExpBuffer(&buf,
@@ -1750,12 +1750,28 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
- "\n AND pg_catalog.pg_relation_is_publishable('%s')"
- "\nORDER BY 1",
+ "\n AND pg_catalog.pg_relation_is_publishable('%s')",
oid);
+ /*
+ * Skip entries where this sequence appears in the publication's
+ * EXCEPT list.
+ */
+ if (pset.sversion >= 200000)
+ {
+ appendPQExpBuffer(&buf,
+ "\n AND NOT EXISTS ("
+ "\n SELECT 1"
+ "\n FROM pg_catalog.pg_publication_rel pr"
+ "\n WHERE pr.prpubid = p.oid AND"
+ "\n pr.prrelid = '%s' AND pr.prexcept)",
+ oid);
+ }
+
+ appendPQExpBuffer(&buf, "\nORDER BY 1");
+
result = PSQLexec(buf.data);
if (result)
{
@@ -1780,6 +1796,42 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 200000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1796,6 +1848,7 @@ describeOneTableDetails(const char *schemaname,
pg_free(footers[0]);
pg_free(footers[1]);
+ pg_free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6727,6 +6780,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6763,7 +6817,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -6817,7 +6871,8 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -6829,7 +6884,7 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
@@ -6837,6 +6892,26 @@ describePublications(const char *pattern)
}
}
+ if (puballsequences)
+ {
+ if (pset.sversion >= 200000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ appendPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
+
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index e4bc2c93145..77b03d7b50b 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3748,6 +3748,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e03556399ab..246bd44e6ac 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4461,14 +4461,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
- RangeVar *relation; /* publication relation */
- Node *whereClause; /* qualifications */
+ RangeVar *relation; /* publication table/sequence */
+ Node *whereClause; /* qualifications for publication table */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4477,6 +4477,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4488,7 +4489,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4505,7 +4506,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..6442b87d038 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -471,8 +471,9 @@ RESET client_min_messages;
DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -483,8 +484,8 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_pub_forallsequences1 | f | t
(1 row)
-\d+ regress_pub_seq0
- Sequence "public.regress_pub_seq0"
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -502,8 +503,8 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
- Sequence "pub_test.regress_pub_seq1"
+\d+ pub_test.regress_seq1
+ Sequence "pub_test.regress_seq1"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -534,10 +535,92 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "public.regress_seq0"
+ "public.regress_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences_except"
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "pub_test.regress_seq2"
+ "public.regress_seq0"
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+CREATE TABLE regress_tab1(a int);
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+ Publication regress_pub_for_allsequences_alltables_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.regress_tab1"
+Except sequences:
+ "public.regress_seq0"
+
+RESET client_min_messages;
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
+ ^
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE regress_tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..11c20e65143 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -221,8 +221,9 @@ DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -230,7 +231,7 @@ CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_forallsequences1';
-\d+ regress_pub_seq0
+\d+ regress_seq0
\dRp+ regress_pub_forallsequences1
SET client_min_messages = 'ERROR';
@@ -238,7 +239,7 @@ CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
+\d+ pub_test.regress_seq1
--- Specifying both ALL TABLES and ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,51 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_seq0
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+CREATE TABLE regress_tab1(a int);
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+RESET client_min_messages;
+
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE regress_tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..c35ef9403ee 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,85 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '1|f', 'sequences in the EXCEPT list are excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence lists
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub_all_seq_except SET PUBLICATION tap_pub_all_seq_except1, tap_pub_all_seq_except2;
+ ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES;
+));
+$synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq_excluded_in_pub1 is excluded in tap_pub_all_seq_except1 but included in
+# tap_pub_all_seq_except2, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq_excluded_in_pub2 is excluded in tap_pub_all_seq_except2 but included in
+# tap_pub_all_seq_except1, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# Cleanup
+$node_subscriber->safe_psql('postgres',
+ 'DROP SUBSCRIPTION tap_sub_all_seq_except');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except1');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 117e7379f10..08aee4b4933 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2475,8 +2475,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
[application/octet-stream] v18-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (34.4K, ../../CANhcyEUmDrWf-cUe9s0OCLvAtLUSNy347nL-8FWqbax+JD4yiA@mail.gmail.com/3-v18-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From 094fdbf4598c74e6754929ad686dc98da9147a4e Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Wed, 1 Jul 2026 12:12:47 +0530
Subject: [PATCH v18 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 51 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 268 ++++++++++++----------
src/backend/parser/gram.y | 2 +-
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 35 +++
src/test/regress/sql/publication.sql | 13 ++
src/test/subscription/t/037_except.pl | 14 ++
9 files changed, 286 insertions(+), 153 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..d82ff3079db 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -73,9 +77,9 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
The third variant either modifies the included tables/schemas
- or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
- <literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ or marks the publication as <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL SEQUENCES</literal>, optionally using
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -273,10 +281,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
</programlisting></para>
<para>
- Reset the publication to be a <literal>FOR ALL TABLES</literal> publication
- with no excluded tables:
+ Reset the publication to be <literal>FOR ALL TABLES</literal> with no
+ exclusions:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5eb4de744de..32850002e58 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -956,7 +956,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -964,6 +964,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -983,8 +985,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -1000,15 +1009,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1016,7 +1026,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1024,7 +1035,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1078,7 +1089,7 @@ GetAllTablesPublications(void)
* it excludes sequences specified in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1087,18 +1098,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index bd171f9e48e..95160c8d846 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1253,26 +1253,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1286,29 +1391,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES/ FOR ALL SEQUENCES mode, relations are
+ * tracked as exclusions (EXCEPT clause). Fetch the current
+ * excluded relations so they can be reconciled with the specified
+ * EXCEPT list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES/ FOR ALL SEQUENCES; otherwise, there
+ * are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1321,118 +1430,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1708,11 +1724,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /*
- * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
- * PUBLICATION.
- */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1736,8 +1750,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2092,10 +2106,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f0be22ed941..18577908de6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11499,7 +11499,7 @@ pub_except_seq_list: PublicationExceptSeqSpec
* pub_all_obj_type is one of:
*
* ALL TABLES [ EXCEPT ( TABLE table_name [, ...] ) ]
- * ALL SEQUENCES
+ * ALL SEQUENCES [ EXCEPT ( SEQUENCE sequence_name [, ...] ) ]
*
*****************************************************************************/
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 77b03d7b50b..7a9c00e1528 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2337,6 +2337,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 6442b87d038..4a02488bffb 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -577,6 +577,25 @@ Except sequences:
"pub_test.regress_seq2"
"public.regress_seq0"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_seq0"
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+(1 row)
+
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
CREATE TABLE regress_tab1(a int);
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
@@ -596,24 +615,40 @@ CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0,
ERROR: syntax error at or near "regress_seq0"
LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
^
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ..._forallsequences_except SET ALL SEQUENCES EXCEPT (regress_se...
+ ^
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for unlogged sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for temporary sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 11c20e65143..92ecfc83aa2 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -269,6 +269,14 @@ CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (
ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
\dRp+ regress_pub_forallsequences_except
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
CREATE TABLE regress_tab1(a int);
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
@@ -277,20 +285,25 @@ RESET client_min_messages;
-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index c35ef9403ee..d3166b213f4 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -289,6 +289,7 @@ $node_publisher->safe_psql(
'postgres', qq (
CREATE TABLE seq_test (v BIGINT);
CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub1_2;
CREATE SEQUENCE seq_excluded_in_pub2;
INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
@@ -297,6 +298,7 @@ $node_publisher->safe_psql(
$node_subscriber->safe_psql(
'postgres', qq(
CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub1_2;
CREATE SEQUENCE seq_excluded_in_pub2;
CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
));
@@ -316,6 +318,18 @@ $result = $node_subscriber->safe_psql('postgres',
"SELECT last_value, is_called FROM seq_excluded_in_pub2");
is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+# Check ALTER PUBLICATION ... ALL SEQUENCES (EXCEPT SEQUENCE ...)
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ ALTER PUBLICATION tap_pub_all_seq_except1 SET ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1, seq_excluded_in_pub1_2);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1_2') FROM generate_series(1,100);)
+);
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES");
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1_2");
+is($result, '1|f', 'sequences in the EXCEPT list are excluded');
+
# ============================================
# Test when a subscription is subscribing to multiple publications
# ============================================
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-01 08:30 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-01 23:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-07-02 06:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-03 05:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-03 09:48 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-07-06 06:04 ` Ashutosh Sharma <[email protected]>
2026-07-07 06:36 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Ashutosh Sharma @ 2026-07-06 06:04 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: Peter Smith <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On Fri, Jul 3, 2026 at 3:18 PM Shlok Kyal <[email protected]> wrote:
>
> On Fri, 3 Jul 2026 at 11:24, Ashutosh Sharma <[email protected]> wrote:
> >
> > Hi,
> >
> > On Thu, Jul 2, 2026 at 11:53 AM Shlok Kyal <[email protected]> wrote:
> > >
> > > Please find the updated v17 patch attached.
> > >
> >
> > Thank you for the updated patches. I see that the following command
> > works with your patch:
> >
> > CREATE PUBLICATION combined_seq_pub_1
> > FOR ALL SEQUENCES
> > EXCEPT (SEQUENCE ONLY s1_seq);
> >
> > May I know what the purpose of ONLY is here?
> >
> > AFAIU, for FOR ALL TABLES, ONLY was added to handle inherited tables.
> > However, sequences do not have inheritance, so I am not sure what
> > semantics ONLY is intended to provide for sequences. Could you please
> > clarify?
> It was unintentional. ONLY should not be allowed with SEQUENCE.
> This happened because, in gram.y, the EXCEPT SEQUENCE production uses
> relation_expr:
> +PublicationExceptSeqSpec:
> + relation_expr
> + {
> + $$ = makeNode(PublicationObjSpec);
> + $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
> + $$->pubrelation = makeNode(PublicationRelation);
> + $$->pubrelation->except = true;
> + $$->pubrelation->relation = $1;
> + $$->location = @1;
> + }
> + ;
> relation_expr accepts ONLY, so it inadvertently allows syntax such as
> EXCEPT (SEQUENCE ONLY s1).
> I think it would be better to use qualified_name instead. That's also
> what CREATE SEQUENCE uses, so it would be consistent with the rest of
> the grammar and would prevent ONLY from being accepted.
>
> With this change we are now throwing syntax error:
> postgres=# CREATE PUBLICATION combined_seq_pub_1
> FOR ALL SEQUENCES
> EXCEPT (SEQUENCE ONLY s1_seq);
> ERROR: syntax error at or near "ONLY"
> LINE 3: EXCEPT (SEQUENCE ONLY s1_seq);
>
> While making the change, I also found that I missed updating the
> comments in gram.y for this feature.
> I have updated the same and attached the updated v18 patches.
>
Thanks for fixing this. I found a few cosmetics issues while reviewing
the latest patches, please see if these make sense to you:
+# Check ALTER PUBLICATION ... ALL SEQUENCES (EXCEPT SEQUENCE ...)
+$node_publisher->safe_psql(
The placement of the EXCEPT clause here doesn't match the actual
supported syntax. The correct form is:
CREATE PUBLICATION combined_pub_seq FOR ALL SEQUENCES EXCEPT ( SEQUENCE ... )
--
+ * Get the list of tables and sequences for publications specified in
+ * the EXCEPT clause.
Above comment doesn't look clearer to me. Perhaps something like "for
tables and sequences specified in the publication's EXCEPT clause"
would read more clearly. What do you think?
--
+ /* Print publications where the sequence is in the EXCEPT clause */
+ if (pset.sversion >= 200000)
This doesn't read well as well, how about "Print publications whose
EXCEPT clause contains this sequence"?
--
+ * In FOR ALL TABLES/ FOR ALL SEQUENCES mode, relations are
This seems to be having uneven spacing. I would rather replace '/'
with 'or' here.
--
With Regards,
Ashutosh Sharma.
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-01 08:30 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-01 23:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-07-02 06:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-03 05:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-03 09:48 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-06 06:04 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
@ 2026-07-07 06:36 ` Shlok Kyal <[email protected]>
2026-07-08 02:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Shlok Kyal @ 2026-07-07 06:36 UTC (permalink / raw)
To: Ashutosh Sharma <[email protected]>; +Cc: Peter Smith <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, 6 Jul 2026 at 11:34, Ashutosh Sharma <[email protected]> wrote:
>
> Thanks for fixing this. I found a few cosmetics issues while reviewing
> the latest patches, please see if these make sense to you:
>
> +# Check ALTER PUBLICATION ... ALL SEQUENCES (EXCEPT SEQUENCE ...)
> +$node_publisher->safe_psql(
>
> The placement of the EXCEPT clause here doesn't match the actual
> supported syntax. The correct form is:
>
> CREATE PUBLICATION combined_pub_seq FOR ALL SEQUENCES EXCEPT ( SEQUENCE ... )
>
> --
>
> + * Get the list of tables and sequences for publications specified in
> + * the EXCEPT clause.
>
> Above comment doesn't look clearer to me. Perhaps something like "for
> tables and sequences specified in the publication's EXCEPT clause"
> would read more clearly. What do you think?
>
> --
>
> + /* Print publications where the sequence is in the EXCEPT clause */
> + if (pset.sversion >= 200000)
>
> This doesn't read well as well, how about "Print publications whose
> EXCEPT clause contains this sequence"?
>
> --
>
> + * In FOR ALL TABLES/ FOR ALL SEQUENCES mode, relations are
>
> This seems to be having uneven spacing. I would rather replace '/'
> with 'or' here.
>
Hi Ashutosh,
Thanks for reviewing the patch.
I agree with your comments and addressed it in the lastest v19 patch.
I also noticed that the CI runs are failing.
It is failing because of the new test added in 0002 patch.
After we alter the publication and change the sequences in the EXCEPT
list, we should run 'ALTER SUBSCRIPTION ... REFRESH PUBLICATION' on
the subscription, so the same is reflected on the pg_subscription_rel.
I have made the change for the same in the updated patch.
Thanks
Shlok Kyal
Attachments:
[application/octet-stream] v19-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (34.6K, ../../CANhcyEUg0c2rMEWt=2DFLF+frxx72AyqXn2viyye5uvb8Fmn0g@mail.gmail.com/2-v19-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From c76246b2902e3c743befc3bcb590a8017499a8ff Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Wed, 1 Jul 2026 12:12:47 +0530
Subject: [PATCH v19 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 51 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 268 ++++++++++++----------
src/backend/parser/gram.y | 2 +-
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 35 +++
src/test/regress/sql/publication.sql | 13 ++
src/test/subscription/t/037_except.pl | 20 ++
9 files changed, 292 insertions(+), 153 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..d82ff3079db 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -73,9 +77,9 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
The third variant either modifies the included tables/schemas
- or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
- <literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ or marks the publication as <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL SEQUENCES</literal>, optionally using
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -273,10 +281,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
</programlisting></para>
<para>
- Reset the publication to be a <literal>FOR ALL TABLES</literal> publication
- with no excluded tables:
+ Reset the publication to be <literal>FOR ALL TABLES</literal> with no
+ exclusions:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 5eb4de744de..32850002e58 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -956,7 +956,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -964,6 +964,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -983,8 +985,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -1000,15 +1009,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1016,7 +1026,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1024,7 +1035,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1078,7 +1089,7 @@ GetAllTablesPublications(void)
* it excludes sequences specified in the EXCEPT clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1087,18 +1098,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index bd171f9e48e..bd1f1cced36 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1253,26 +1253,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1286,29 +1391,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES or FOR ALL SEQUENCES mode, relations are
+ * tracked as exclusions (EXCEPT clause). Fetch the current
+ * excluded relations so they can be reconciled with the specified
+ * EXCEPT list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES or FOR ALL SEQUENCES; otherwise,
+ * there are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1321,118 +1430,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1708,11 +1724,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /*
- * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
- * PUBLICATION.
- */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1736,8 +1750,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2092,10 +2106,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f0be22ed941..18577908de6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11499,7 +11499,7 @@ pub_except_seq_list: PublicationExceptSeqSpec
* pub_all_obj_type is one of:
*
* ALL TABLES [ EXCEPT ( TABLE table_name [, ...] ) ]
- * ALL SEQUENCES
+ * ALL SEQUENCES [ EXCEPT ( SEQUENCE sequence_name [, ...] ) ]
*
*****************************************************************************/
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 155087f572b..105adb87874 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2337,6 +2337,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 6442b87d038..4a02488bffb 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -577,6 +577,25 @@ Except sequences:
"pub_test.regress_seq2"
"public.regress_seq0"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_seq0"
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+(1 row)
+
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
CREATE TABLE regress_tab1(a int);
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
@@ -596,24 +615,40 @@ CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0,
ERROR: syntax error at or near "regress_seq0"
LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
^
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ..._forallsequences_except SET ALL SEQUENCES EXCEPT (regress_se...
+ ^
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for unlogged sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for temporary sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 11c20e65143..92ecfc83aa2 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -269,6 +269,14 @@ CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (
ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
\dRp+ regress_pub_forallsequences_except
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+
-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
CREATE TABLE regress_tab1(a int);
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
@@ -277,20 +285,25 @@ RESET client_min_messages;
-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index c220d2a0b5d..77fd09b222f 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -289,6 +289,7 @@ $node_publisher->safe_psql(
'postgres', qq (
CREATE TABLE seq_test (v BIGINT);
CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub1_2;
CREATE SEQUENCE seq_excluded_in_pub2;
INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
@@ -297,6 +298,7 @@ $node_publisher->safe_psql(
$node_subscriber->safe_psql(
'postgres', qq(
CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub1_2;
CREATE SEQUENCE seq_excluded_in_pub2;
CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
));
@@ -316,6 +318,24 @@ $result = $node_subscriber->safe_psql('postgres',
"SELECT last_value, is_called FROM seq_excluded_in_pub2");
is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+# Check ALTER PUBLICATION ... ALL SEQUENCES EXCEPT (SEQUENCE ...)
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ ALTER PUBLICATION tap_pub_all_seq_except1 SET ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1, seq_excluded_in_pub1_2);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1_2') FROM generate_series(1,100);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH PUBLICATION;
+ ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES;
+));
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1_2");
+is($result, '1|f', 'sequences in the EXCEPT list are excluded');
+
# ============================================
# Test when a subscription is subscribing to multiple publications
# ============================================
--
2.34.1
[application/octet-stream] v19-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (67.0K, ../../CANhcyEUg0c2rMEWt=2DFLF+frxx72AyqXn2viyye5uvb8Fmn0g@mail.gmail.com/3-v19-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From b13bb13c53b4d7e69076fb87c3b509215f21aea8 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v19 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 8 +-
doc/src/sgml/ref/create_publication.sgml | 46 ++++++---
src/backend/catalog/pg_publication.c | 70 ++++++++-----
src/backend/commands/publicationcmds.c | 118 +++++++++++++--------
src/backend/parser/gram.y | 119 ++++++++++++++--------
src/bin/pg_dump/pg_dump.c | 65 +++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 ++++
src/bin/psql/describe.c | 89 ++++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 15 +--
src/test/regress/expected/publication.out | 99 ++++++++++++++++--
src/test/regress/sql/publication.sql | 52 +++++++++-
src/test/subscription/t/037_except.pl | 77 ++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 634 insertions(+), 176 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index f21239af6b8..a2961bd3c41 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -118,8 +118,12 @@
synchronized at any time. For more information, see
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
- be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ be explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 85cfcaddafa..360dc7eda6a 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,22 +193,26 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
- Once a table is excluded, the exclusion applies to that table
- regardless of its name or schema. Renaming the table or moving it to
- another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
- not remove the exclusion.
+ Once a table or sequence is excluded, the exclusion applies to that
+ object regardless of its name or schema. Renaming the object or moving
+ it to another schema using <command>ALTER TABLE ... SET SCHEMA</command>
+ or <command>ALTER SEQUENCE ... SET SCHEMA</command> does not remove the
+ exclusion.
</para>
<para>
For inherited tables, if <literal>ONLY</literal> is specified before the
@@ -223,9 +231,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -553,11 +562,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 1ec94c851b2..5eb4de744de 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause. The relkind
+ * of the relation in 'pri' is checked for compatibility against it. Error is
+ * raised if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -104,11 +122,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -531,7 +553,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -569,7 +592,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -989,15 +1012,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1048,8 +1073,9 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1063,11 +1089,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..bd171f9e48e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -176,12 +176,13 @@ parse_publication_options(ParseState *pstate,
}
/*
- * Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * Convert the PublicationObjSpecType list into PublicationRelation lists
+ * (`rels`, `excepttbls`, `exceptseqs`) and a schema oid list (`schemas`).
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,29 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
- List *rels;
+ List *rels = OpenRelationList(excepttbls);
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +986,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +994,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1272,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1283,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1306,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1380,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1427,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1668,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1700,19 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /*
+ * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
+ * PUBLICATION.
+ */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1735,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1854,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1872,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2011,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2056,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2074,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..f0be22ed941 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11265,7 +11267,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
* pub_all_obj_type is one of:
*
* TABLES [EXCEPT (TABLE table [, ...] )]
- * SEQUENCES
+ * SEQUENCES [EXCEPT (SEQUENCE sequence [, ...] )]
*
* CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
*
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+PublicationExceptSeqSpec:
+ qualified_name
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+pub_except_tbl_list: PublicationExceptTblSpec
+ { $$ = list_make1($1); }
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20747,12 +20775,12 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
bool *all_tables, bool *all_sequences,
core_yyscan_t yyscanner)
{
- if (!all_objects_list)
- return;
-
*all_tables = false;
*all_sequences = false;
+ if (!all_objects_list)
+ return;
+
foreach_ptr(PublicationAllObjSpec, obj, all_objects_list)
{
if (obj->pubobjtype == PUBLICATION_ALL_TABLES)
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f67daf85911..5cf2ead6d1f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4571,23 +4571,31 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences specified in the publication's
+ * EXCEPT clause.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
* collect this information, which is then emitted by
* dumpPublication().
+ *
+ * Note: EXCEPT (TABLE ...) was introduced in PG19, whereas EXCEPT
+ * (SEQUENCE ...) was introduced in PG20. Hence, the version checks
+ * below differ.
*/
if (fout->remoteVersion >= 190000)
{
@@ -4596,9 +4604,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4608,14 +4617,26 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ /* EXCEPT (SEQUENCE ...) is supported from PG20 onwards. */
+ if (fout->remoteVersion >= 200000 &&
+ relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ else
+ Assert(false);
+ }
}
PQclear(res_tbls);
@@ -4675,12 +4696,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+ const char *seqname = fmtQualifiedDumpable(tbinfo);
+
+ if (++n_except == 1)
+ appendPQExpBuffer(query, " EXCEPT (SEQUENCE %s", seqname);
+ else
+ appendPQExpBuffer(query, ", %s", seqname);
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..9a0673575d2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3283,6 +3283,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql => 'CREATE SEQUENCE test_except_seq;
+ CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4082,6 +4102,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index fef86b4cca3..e8ad71ba583 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1672,7 +1672,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
appendPQExpBuffer(&buf,
@@ -1750,12 +1750,28 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
- "\n AND pg_catalog.pg_relation_is_publishable('%s')"
- "\nORDER BY 1",
+ "\n AND pg_catalog.pg_relation_is_publishable('%s')",
oid);
+ /*
+ * Skip entries where this sequence appears in the publication's
+ * EXCEPT list.
+ */
+ if (pset.sversion >= 200000)
+ {
+ appendPQExpBuffer(&buf,
+ "\n AND NOT EXISTS ("
+ "\n SELECT 1"
+ "\n FROM pg_catalog.pg_publication_rel pr"
+ "\n WHERE pr.prpubid = p.oid AND"
+ "\n pr.prrelid = '%s' AND pr.prexcept)",
+ oid);
+ }
+
+ appendPQExpBuffer(&buf, "\nORDER BY 1");
+
result = PSQLexec(buf.data);
if (result)
{
@@ -1780,6 +1796,42 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* Print publications whose EXCEPT clause contains this sequence */
+ if (pset.sversion >= 200000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1796,6 +1848,7 @@ describeOneTableDetails(const char *schemaname,
pg_free(footers[0]);
pg_free(footers[1]);
+ pg_free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6727,6 +6780,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6763,7 +6817,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -6817,7 +6871,8 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
@@ -6829,7 +6884,7 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
@@ -6837,6 +6892,26 @@ describePublications(const char *pattern)
}
}
+ if (puballsequences)
+ {
+ if (pset.sversion >= 200000)
+ {
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ appendPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
+
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 49ea584cd4f..155087f572b 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3748,6 +3748,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e03556399ab..246bd44e6ac 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4461,14 +4461,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
- RangeVar *relation; /* publication relation */
- Node *whereClause; /* qualifications */
+ RangeVar *relation; /* publication table/sequence */
+ Node *whereClause; /* qualifications for publication table */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
/*
* Publication object type
@@ -4477,6 +4477,7 @@ typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4488,7 +4489,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4505,7 +4506,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..6442b87d038 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -471,8 +471,9 @@ RESET client_min_messages;
DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -483,8 +484,8 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_pub_forallsequences1 | f | t
(1 row)
-\d+ regress_pub_seq0
- Sequence "public.regress_pub_seq0"
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -502,8 +503,8 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
- Sequence "pub_test.regress_pub_seq1"
+\d+ pub_test.regress_seq1
+ Sequence "pub_test.regress_seq1"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -534,10 +535,92 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "public.regress_seq0"
+ "public.regress_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences_except"
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "pub_test.regress_seq2"
+ "public.regress_seq0"
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+CREATE TABLE regress_tab1(a int);
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+ Publication regress_pub_for_allsequences_alltables_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.regress_tab1"
+Except sequences:
+ "public.regress_seq0"
+
+RESET client_min_messages;
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
+ ^
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE regress_tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..11c20e65143 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -221,8 +221,9 @@ DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -230,7 +231,7 @@ CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_forallsequences1';
-\d+ regress_pub_seq0
+\d+ regress_seq0
\dRp+ regress_pub_forallsequences1
SET client_min_messages = 'ERROR';
@@ -238,7 +239,7 @@ CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
+\d+ pub_test.regress_seq1
--- Specifying both ALL TABLES and ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,51 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+-- Check that the sequence description shows the publications where it is listed
+-- in the EXCEPT clause
+\d+ regress_seq0
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+CREATE TABLE regress_tab1(a int);
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+RESET client_min_messages;
+
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE regress_tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..c220d2a0b5d 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,83 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '1|f', 'sequences in the EXCEPT list are excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence lists
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub_all_seq_except SET PUBLICATION tap_pub_all_seq_except1, tap_pub_all_seq_except2;
+ ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES;
+));
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq_excluded_in_pub1 is excluded in tap_pub_all_seq_except1 but included in
+# tap_pub_all_seq_except2, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# seq_excluded_in_pub2 is excluded in tap_pub_all_seq_except2 but included in
+# tap_pub_all_seq_except1, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one publication but included by another'
+);
+
+# Cleanup
+$node_subscriber->safe_psql('postgres',
+ 'DROP SUBSCRIPTION tap_sub_all_seq_except');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except1');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ffb413ab612..feacd880bd9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2475,8 +2475,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-01 08:30 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-01 23:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-07-02 06:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-03 05:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-03 09:48 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-06 06:04 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-07 06:36 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-07-08 02:25 ` Peter Smith <[email protected]>
2026-07-08 12:08 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
0 siblings, 1 reply; 99+ messages in thread
From: Peter Smith @ 2026-07-08 02:25 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: Ashutosh Sharma <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Some review comments for v19-0001 (no comments for v19-0002)
//////////
Patch 0001
//////////
NOTE: The following review comments are all coming from the same PoV
that actually there can be multiple simultaneous EXCEPT clauses (e.g.
FOR ALL TABLES EXCEPT, and FOR ALL SEQUENCES EXCEPT), so it is not
quite correct to just refer to the singular "the EXCEPT clause" --
should use plural, or remove the ambiguity from the text.
======
src/backend/catalog/pg_publication.c
GetAllTablesPublications:
1.
- * For a FOR ALL TABLES publication, the returned list excludes
tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
+ * it excludes sequences specified in the EXCEPT clause.
SUGGESTION
For a FOR ALL TABLES publication, the returned list excludes tables
specified in the EXCEPT (TABLE ...) clause. For a FOR ALL SEQUENCES
publication, it excludes sequences specified in the EXCEPT (SEQUENCE
...) clause.
======
src/bin/pg_dump/pg_dump.c
getPublications:
2.
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences specified in the publication's
+ * EXCEPT clause.
You cannot exclude tables and sequences from the same EXCEPT, so that
should say "EXCEPT clauses" (plural). But, it may be simpler to just
reword it.
SUGGESTION
Get the list of tables and sequences explicitly excluded from the publication.
======
src/bin/psql/describe.c
describeOneTableDetails:
3.
+ /* Print publications whose EXCEPT clause contains this sequence */
+ if (pset.sversion >= 200000)
This is similar. "whose EXCEPT clause" implies there is only one, but
in practice there can be multiple EXCEPT clauses: (FOR ALL TABLES
EXCEPT + FOR ALL SEQUENCES EXCEPT).
SUGGESTION
Print publications that explicitly exclude this sequence.
~~~
4.
+ /* Get sequences in the EXCEPT clause for this publication */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
This is similar. "the EXCEPT clause" implies there is only one, but
there may be multiple. I saw that you were mimicking the existing
comment, so those should be the same.
SUGGESTION #1 (redundant, because the next string already says what
the code is doing)
Just remove both comments.
SUGGESTION #2 (clarify what clauses you are talking about)
/* Get tables in the EXCEPT (TABLE ...) clause for this publication */
and
/* Get sequences in the EXCEPT (SEQUENCE ...) clause for this publication */
======
src/include/nodes/parsenodes.h
5.
+typedef struct PublicationRelation
{
NodeTag type;
- RangeVar *relation; /* publication relation */
- Node *whereClause; /* qualifications */
+ RangeVar *relation; /* publication table/sequence */
+ Node *whereClause; /* qualifications for publication table */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
Same. /True if listed in the EXCEPT clause/True if listed in an EXCEPT clause/
~~~
6.
PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
/A table in the EXCEPT clause/A table in an EXCEPT clause/
/A sequence in the EXCEPT clause/A sequence in an EXCEPT clause/
~~~
7.
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in the EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
/specified in the EXCEPT clause/specified in an EXCEPT clause/
======
src/test/regress/sql/publication.sql
8.
+-- Check that the sequence description shows the publications where
it is listed
+-- in the EXCEPT clause
/the EXCEPT clause/an EXCEPT clause/
~~~
9.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
/with EXCEPT clause/, each with an EXCEPT clause/
======
src/test/subscription/t/037_except.pl
10.
+is($result, '200|t',
+ 'check replication of a sequence in the EXCEPT clause of one
publication but included by another'
+);
(same x2)
SUGGESTION
check replication of a sequence excluded by one publication but
included by another
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-01 08:30 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-01 23:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-07-02 06:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-03 05:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-03 09:48 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-06 06:04 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-07 06:36 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-08 02:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-07-08 12:08 ` Shlok Kyal <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Shlok Kyal @ 2026-07-08 12:08 UTC (permalink / raw)
To: Peter Smith <[email protected]>; +Cc: Ashutosh Sharma <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, 8 Jul 2026 at 07:56, Peter Smith <[email protected]> wrote:
>
> Some review comments for v19-0001 (no comments for v19-0002)
>
> //////////
> Patch 0001
> //////////
>
> NOTE: The following review comments are all coming from the same PoV
> that actually there can be multiple simultaneous EXCEPT clauses (e.g.
> FOR ALL TABLES EXCEPT, and FOR ALL SEQUENCES EXCEPT), so it is not
> quite correct to just refer to the singular "the EXCEPT clause" --
> should use plural, or remove the ambiguity from the text.
>
> ======
> src/backend/catalog/pg_publication.c
>
> GetAllTablesPublications:
>
> 1.
> - * For a FOR ALL TABLES publication, the returned list excludes
> tables mentioned
> - * in the EXCEPT clause.
> + * For a FOR ALL TABLES publication, the returned list excludes tables
> + * specified in the EXCEPT clause. For a FOR ALL SEQUENCES publication,
> + * it excludes sequences specified in the EXCEPT clause.
>
> SUGGESTION
> For a FOR ALL TABLES publication, the returned list excludes tables
> specified in the EXCEPT (TABLE ...) clause. For a FOR ALL SEQUENCES
> publication, it excludes sequences specified in the EXCEPT (SEQUENCE
> ...) clause.
>
> ======
> src/bin/pg_dump/pg_dump.c
>
> getPublications:
>
> 2.
> - * Get the list of tables for publications specified in the EXCEPT
> - * TABLE clause.
> + * Get the list of tables and sequences specified in the publication's
> + * EXCEPT clause.
>
> You cannot exclude tables and sequences from the same EXCEPT, so that
> should say "EXCEPT clauses" (plural). But, it may be simpler to just
> reword it.
>
> SUGGESTION
> Get the list of tables and sequences explicitly excluded from the publication.
>
> ======
> src/bin/psql/describe.c
>
> describeOneTableDetails:
>
> 3.
> + /* Print publications whose EXCEPT clause contains this sequence */
> + if (pset.sversion >= 200000)
>
> This is similar. "whose EXCEPT clause" implies there is only one, but
> in practice there can be multiple EXCEPT clauses: (FOR ALL TABLES
> EXCEPT + FOR ALL SEQUENCES EXCEPT).
>
> SUGGESTION
> Print publications that explicitly exclude this sequence.
>
> ~~~
>
> 4.
> + /* Get sequences in the EXCEPT clause for this publication */
> + printfPQExpBuffer(&buf, "/* %s */\n",
> + _("Get sequences excluded by this publication"));
>
> This is similar. "the EXCEPT clause" implies there is only one, but
> there may be multiple. I saw that you were mimicking the existing
> comment, so those should be the same.
>
> SUGGESTION #1 (redundant, because the next string already says what
> the code is doing)
> Just remove both comments.
>
> SUGGESTION #2 (clarify what clauses you are talking about)
> /* Get tables in the EXCEPT (TABLE ...) clause for this publication */
> and
> /* Get sequences in the EXCEPT (SEQUENCE ...) clause for this publication */
>
> ======
> src/include/nodes/parsenodes.h
>
> 5.
> +typedef struct PublicationRelation
> {
> NodeTag type;
> - RangeVar *relation; /* publication relation */
> - Node *whereClause; /* qualifications */
> + RangeVar *relation; /* publication table/sequence */
> + Node *whereClause; /* qualifications for publication table */
> List *columns; /* List of columns in a publication table */
> bool except; /* True if listed in the EXCEPT clause */
> -} PublicationTable;
> +} PublicationRelation;
>
> Same. /True if listed in the EXCEPT clause/True if listed in an EXCEPT clause/
>
> ~~~
>
> 6.
> PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
> + PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in the EXCEPT clause */
>
> /A table in the EXCEPT clause/A table in an EXCEPT clause/
> /A sequence in the EXCEPT clause/A sequence in an EXCEPT clause/
>
> ~~~
>
> 7.
> - List *except_tables; /* tables specified in the EXCEPT clause */
> + List *except_relations; /* depending on the 'pubobjtype', this is
> + * a list of either tables or sequences
> + * specified in the EXCEPT clause */
> ParseLoc location; /* token location, or -1 if unknown */
> } PublicationAllObjSpec;
>
> /specified in the EXCEPT clause/specified in an EXCEPT clause/
>
> ======
> src/test/regress/sql/publication.sql
>
> 8.
> +-- Check that the sequence description shows the publications where
> it is listed
> +-- in the EXCEPT clause
>
> /the EXCEPT clause/an EXCEPT clause/
>
> ~~~
>
> 9.
> +-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
>
> /with EXCEPT clause/, each with an EXCEPT clause/
>
>
> ======
> src/test/subscription/t/037_except.pl
>
> 10.
> +is($result, '200|t',
> + 'check replication of a sequence in the EXCEPT clause of one
> publication but included by another'
> +);
>
> (same x2)
>
> SUGGESTION
> check replication of a sequence excluded by one publication but
> included by another
>
I have addressed the comments and attached the updated v20 patch.
Thanks
Shlok Kyal
Attachments:
[application/x-patch] v20-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch (34.6K, ../../CANhcyEVe8HdiXv-_8QtXBjwGmCrwCYkqA-adBZJh=QY4HWB6jQ@mail.gmail.com/2-v20-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLIC.patch)
download | inline diff:
From 129e1df8fb1c6dcde2c286dbc6e3d0114c36c517 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Wed, 8 Jul 2026 16:50:15 +0530
Subject: [PATCH v20 2/2] Support EXCEPT for ALL SEQUENCES in ALTER PUBLICATION
Extend ALTER PUBLICATION to support an EXCEPT clause when using
ALL SEQUENCES, allowing specific sequences to be excluded from the
publication.
If the EXCEPT clause is specified, the existing exclusion list for the
publication is replaced with the provided sequences. If the EXCEPT
clause is omitted, any existing exclusions for sequences are cleared.
Example:
ALTER PUBLICATION pub1 SET ALL SEQUENCES;
This clears any existing sequence exclusions for the publication.
ALTER PUBLICATION pub1 SET ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
This sets the exclusion list to the specified sequences.
---
doc/src/sgml/ref/alter_publication.sgml | 51 +++-
src/backend/catalog/pg_publication.c | 36 ++-
src/backend/commands/publicationcmds.c | 268 ++++++++++++----------
src/backend/parser/gram.y | 2 +-
src/bin/psql/tab-complete.in.c | 14 ++
src/include/catalog/pg_publication.h | 6 +-
src/test/regress/expected/publication.out | 35 +++
src/test/regress/sql/publication.sql | 13 ++
src/test/subscription/t/037_except.pl | 20 ++
9 files changed, 292 insertions(+), 153 deletions(-)
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index 52114a16a39..d82ff3079db 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -36,7 +36,7 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">publication_drop_object</replaceable> is one of:</phrase>
@@ -54,6 +54,10 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -73,9 +77,9 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
<para>
The third variant either modifies the included tables/schemas
- or marks the publication as <literal>FOR ALL SEQUENCES</literal> or
- <literal>FOR ALL TABLES</literal>, optionally using
- <literal>EXCEPT</literal> to exclude specific tables. The
+ or marks the publication as <literal>FOR ALL TABLES</literal> or
+ <literal>FOR ALL SEQUENCES</literal>, optionally using
+ <literal>EXCEPT</literal> to exclude specific tables or sequences. The
<literal>SET ALL TABLES</literal> clause can transform an empty publication,
or one defined for <literal>ALL SEQUENCES</literal> (or both
<literal>ALL TABLES</literal> and <literal>ALL SEQUENCES</literal>), into
@@ -86,11 +90,15 @@ ALTER PUBLICATION <replaceable class="parameter">name</replaceable> RENAME TO <r
publication defined for <literal>ALL SEQUENCES</literal>. In addition,
<literal>SET ALL TABLES</literal> can be used to update the tables specified
in the <literal>EXCEPT</literal> clause of a
- <literal>FOR ALL TABLES</literal> publication. If <literal>EXCEPT</literal>
- is specified with a list of tables, the existing exclusion list is replaced
- with the specified tables. If <literal>EXCEPT</literal> is omitted, the
- existing exclusion list is cleared. The <literal>SET</literal> clause, when
- used with a publication defined with <literal>FOR TABLE</literal> or
+ <literal>FOR ALL TABLES</literal> publication and
+ <literal>SET ALL SEQUENCES</literal> can be used to update the sequences
+ specified in the <literal>EXCEPT</literal> clause of a
+ <literal>FOR ALL SEQUENCES</literal> publication. If
+ <literal>EXCEPT</literal> is specified with a list of tables or sequences,
+ the existing exclusion list is replaced with the specified tables or
+ sequences. If <literal>EXCEPT</literal> is omitted, the existing exclusion
+ list is cleared. The <literal>SET</literal> clause, when used with a
+ publication defined with <literal>FOR TABLE</literal> or
<literal>FOR TABLES IN SCHEMA</literal>, replaces the list of tables/schemas
in the publication with the specified list; the existing tables or schemas
that were present in the publication will be removed.
@@ -273,10 +281,31 @@ ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments)
</programlisting></para>
<para>
- Reset the publication to be a <literal>FOR ALL TABLES</literal> publication
- with no excluded tables:
+ Reset the publication to be <literal>FOR ALL TABLES</literal> with no
+ exclusions:
<programlisting>
ALTER PUBLICATION mypublication SET ALL TABLES;
+</programlisting></para>
+
+ <para>
+ Reset the publication to be <literal>ALL SEQUENCES</literal> with no
+ exclusions:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES;
+</programlisting></para>
+
+ <para>
+ Replace the sequence list in the publication's <literal>EXCEPT</literal>
+ clause:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting></para>
+
+ <para>
+ Replace the table and sequence list in the publication's
+ <literal>EXCEPT</literal> clauses:
+<programlisting>
+ALTER PUBLICATION mypublication SET ALL TABLES EXCEPT (TABLE users, departments), ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
</programlisting></para>
<para>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 1d14600255a..b645a096219 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -956,7 +956,7 @@ GetRelationExcludedPublications(Oid relid)
*/
static List *
get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
- bool except_flag)
+ bool except_flag, char pubrelkind)
{
List *result;
Relation pubrelsrel;
@@ -964,6 +964,8 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
SysScanDesc scan;
HeapTuple tup;
+ Assert(pubrelkind == RELKIND_RELATION || pubrelkind == RELKIND_SEQUENCE);
+
/* Find all relations associated with the publication. */
pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
@@ -983,8 +985,15 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
if (except_flag == pubrel->prexcept)
- result = GetPubPartitionOptionRelations(result, pub_partopt,
- pubrel->prrelid);
+ {
+ char relkind = get_rel_relkind(pubrel->prrelid);
+
+ if ((pubrelkind == RELKIND_RELATION &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
+ result = GetPubPartitionOptionRelations(result, pub_partopt,
+ pubrel->prrelid);
+ }
}
systable_endscan(scan);
@@ -1000,15 +1009,16 @@ get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
/*
* Gets list of relation oids that are associated with a publication.
*
- * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
- * should use GetAllPublicationRelations().
+ * This is mainly used for FOR TABLE publications and must not be called for
+ * ALL TABLES publications. For ALL SEQUENCES publications, the result is an
+ * empty list.
*/
List *
GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
Assert(!GetPublication(pubid)->alltables);
- return get_publication_relations(pubid, pub_partopt, false);
+ return get_publication_relations(pubid, pub_partopt, false, RELKIND_RELATION);
}
/*
@@ -1016,7 +1026,8 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
* 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt,
+ char pubrelkind)
{
#ifdef USE_ASSERT_CHECKING
Publication *pub = GetPublication(pubid);
@@ -1024,7 +1035,7 @@ GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Assert(pub->alltables || pub->allsequences);
#endif
- return get_publication_relations(pubid, pub_partopt, true);
+ return get_publication_relations(pubid, pub_partopt, true, pubrelkind);
}
/*
@@ -1079,7 +1090,7 @@ GetAllTablesPublications(void)
* clause.
*/
List *
-GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
+GetAllPublicationRelations(Oid pubid, char pubrelkind, bool pubviaroot)
{
Relation classRel;
ScanKeyData key[1];
@@ -1088,18 +1099,19 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
List *result = NIL;
List *exceptlist = NIL;
- Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
+ Assert(!(pubrelkind == RELKIND_SEQUENCE && pubviaroot));
exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ PUBLICATION_PART_LEAF,
+ pubrelkind);
classRel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_relkind,
BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(relkind));
+ CharGetDatum(pubrelkind));
scan = table_beginscan_catalog(classRel, 1, key);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index bd171f9e48e..bd1f1cced36 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -66,7 +66,7 @@ static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
AlterPublicationStmt *stmt, char pubrelkind);
-static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
+static void PublicationDropRelations(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
@@ -1253,26 +1253,131 @@ InvalidatePublicationRels(List *relids)
}
/*
- * Add or remove table to/from publication.
+ * Return the list of tables/sequences to be removed from a publication during
+ * ALTER PUBLICATION ... SET.
+ *
+ * 'rels' contains the relations specified by the SET command, and 'oldrelids'
+ * contains the existing relations in the publication. The function returns the
+ * existing relations that are not present in 'rels' and therefore need to be
+ * removed.
+ */
+static List *
+get_delete_rels(Oid pubid, List *rels, List *oldrelids)
+{
+ List *delrels = NIL;
+
+ foreach_oid(oldrelid, oldrelids)
+ {
+ ListCell *newlc;
+ PublicationRelInfo *oldrel;
+ bool found = false;
+ HeapTuple rftuple;
+ Node *oldrelwhereclause = NULL;
+ Bitmapset *oldcolumns = NULL;
+
+ /* Look up the cache for the old relmap */
+ rftuple = SearchSysCache2(PUBLICATIONRELMAP,
+ ObjectIdGetDatum(oldrelid),
+ ObjectIdGetDatum(pubid));
+
+ /*
+ * See if the existing relation currently has a WHERE clause or a
+ * column list. We need to compare those too.
+ */
+ if (HeapTupleIsValid(rftuple))
+ {
+ bool isnull = true;
+ Datum whereClauseDatum;
+ Datum columnListDatum;
+
+ /* Load the WHERE clause for this table. */
+ whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prqual,
+ &isnull);
+ if (!isnull)
+ oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
+
+ /* Transform the int2vector column list to a bitmap. */
+ columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ if (!isnull)
+ oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
+
+ ReleaseSysCache(rftuple);
+ }
+
+ /*
+ * Check if any of the new set of relations matches with the existing
+ * relations in the publication. Additionally, if the relation has an
+ * associated WHERE clause, check the WHERE expressions also match.
+ * Same for the column list. Drop the rest.
+ */
+ foreach(newlc, rels)
+ {
+ PublicationRelInfo *newpubrel;
+ Oid newrelid;
+ Bitmapset *newcolumns = NULL;
+
+ newpubrel = (PublicationRelInfo *) lfirst(newlc);
+ newrelid = RelationGetRelid(newpubrel->relation);
+
+ /*
+ * Validate the column list. If the column list or WHERE clause
+ * changes, then the validation done here will be duplicated
+ * inside PublicationAddRelations(). The validation is cheap
+ * enough that that seems harmless.
+ */
+ newcolumns = pub_collist_validate(newpubrel->relation,
+ newpubrel->columns);
+
+ found = (newrelid == oldrelid) &&
+ equal(oldrelwhereclause, newpubrel->whereClause) &&
+ bms_equal(oldcolumns, newcolumns);
+
+ if (found)
+ break;
+ }
+
+ /*
+ * Add non-matching relations to the drop list. The relation will be
+ * dropped irrespective of the column list and WHERE clause.
+ */
+ if (!found)
+ {
+ oldrel = palloc0_object(PublicationRelInfo);
+ oldrel->relation = table_open(oldrelid,
+ ShareUpdateExclusiveLock);
+ delrels = lappend(delrels, oldrel);
+ }
+ }
+
+ return delrels;
+}
+
+/*
+ * Add or remove table or sequence to/from publication.
*/
static void
-AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
- List *tables, const char *queryString,
- bool publish_schema)
+AlterPublicationRelations(AlterPublicationStmt *stmt, HeapTuple tup,
+ List *tables, List *sequences, const char *queryString,
+ bool publish_schema)
{
List *rels = NIL;
+ List *seqs = NIL;
Form_pg_publication pubform = (Form_pg_publication) GETSTRUCT(tup);
Oid pubid = pubform->oid;
/*
- * Nothing to do if no objects, except in SET: for that it is quite
- * possible that user has not specified any tables in which case we need
- * to remove all the existing tables.
+ * Nothing to do if no objects were specified, unless this is a SET
+ * command, which may need to remove all existing tables and sequences.
*/
- if (!tables && stmt->action != AP_SetObjects)
+ if (!tables && !sequences && stmt->action != AP_SetObjects)
return;
rels = OpenRelationList(tables);
+ seqs = OpenRelationList(sequences);
if (stmt->action == AP_AddObjects)
{
@@ -1286,29 +1391,33 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
- PublicationDropTables(pubid, rels, false);
+ PublicationDropRelations(pubid, rels, false);
else /* AP_SetObjects */
{
List *oldrelids = NIL;
+ List *oldseqids = NIL;
List *delrels = NIL;
- ListCell *oldlc;
if (stmt->for_all_tables || stmt->for_all_sequences)
{
/*
- * In FOR ALL TABLES mode, relations are tracked as exclusions
- * (EXCEPT clause). Fetch the current excluded relations so they
- * can be reconciled with the specified EXCEPT list.
+ * In FOR ALL TABLES or FOR ALL SEQUENCES mode, relations are
+ * tracked as exclusions (EXCEPT clause). Fetch the current
+ * excluded relations so they can be reconciled with the specified
+ * EXCEPT list.
*
* This applies only if the existing publication is already
- * defined as FOR ALL TABLES; otherwise, there are no exclusion
- * entries to process.
+ * defined as FOR ALL TABLES or FOR ALL SEQUENCES; otherwise,
+ * there are no exclusion entries to process.
*/
if (pubform->puballtables)
- {
oldrelids = GetExcludedPublicationRelations(pubid,
- PUBLICATION_PART_ROOT);
- }
+ PUBLICATION_PART_ROOT,
+ RELKIND_RELATION);
+ if (pubform->puballsequences)
+ oldseqids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT,
+ RELKIND_SEQUENCE);
}
else
{
@@ -1321,118 +1430,25 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
pubform->pubviaroot);
}
- /*
- * To recreate the relation list for the publication, look for
- * existing relations that do not need to be dropped.
- */
- foreach(oldlc, oldrelids)
- {
- Oid oldrelid = lfirst_oid(oldlc);
- ListCell *newlc;
- PublicationRelInfo *oldrel;
- bool found = false;
- HeapTuple rftuple;
- Node *oldrelwhereclause = NULL;
- Bitmapset *oldcolumns = NULL;
-
- /* look up the cache for the old relmap */
- rftuple = SearchSysCache2(PUBLICATIONRELMAP,
- ObjectIdGetDatum(oldrelid),
- ObjectIdGetDatum(pubid));
-
- /*
- * See if the existing relation currently has a WHERE clause or a
- * column list. We need to compare those too.
- */
- if (HeapTupleIsValid(rftuple))
- {
- bool isnull = true;
- Datum whereClauseDatum;
- Datum columnListDatum;
-
- /* Load the WHERE clause for this table. */
- whereClauseDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prqual,
- &isnull);
- if (!isnull)
- oldrelwhereclause = stringToNode(TextDatumGetCString(whereClauseDatum));
-
- /* Transform the int2vector column list to a bitmap. */
- columnListDatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
- Anum_pg_publication_rel_prattrs,
- &isnull);
-
- if (!isnull)
- oldcolumns = pub_collist_to_bitmapset(NULL, columnListDatum, NULL);
-
- ReleaseSysCache(rftuple);
- }
-
- foreach(newlc, rels)
- {
- PublicationRelInfo *newpubrel;
- Oid newrelid;
- Bitmapset *newcolumns = NULL;
-
- newpubrel = (PublicationRelInfo *) lfirst(newlc);
- newrelid = RelationGetRelid(newpubrel->relation);
-
- /*
- * Validate the column list. If the column list or WHERE
- * clause changes, then the validation done here will be
- * duplicated inside PublicationAddRelations(). The
- * validation is cheap enough that that seems harmless.
- */
- newcolumns = pub_collist_validate(newpubrel->relation,
- newpubrel->columns);
-
- /*
- * Check if any of the new set of relations matches with the
- * existing relations in the publication. Additionally, if the
- * relation has an associated WHERE clause, check the WHERE
- * expressions also match. Same for the column list. Drop the
- * rest.
- */
- if (newrelid == oldrelid)
- {
- if (equal(oldrelwhereclause, newpubrel->whereClause) &&
- bms_equal(oldcolumns, newcolumns))
- {
- found = true;
- break;
- }
- }
- }
-
- /*
- * Add the non-matched relations to a list so that they can be
- * dropped.
- */
- if (!found)
- {
- oldrel = palloc_object(PublicationRelInfo);
- oldrel->whereClause = NULL;
- oldrel->columns = NIL;
- oldrel->except = false;
- oldrel->relation = table_open(oldrelid,
- ShareUpdateExclusiveLock);
- delrels = lappend(delrels, oldrel);
- }
- }
+ /* Get tables and sequences to be dropped */
+ delrels = get_delete_rels(pubid, rels, oldrelids);
+ delrels = list_concat(delrels, get_delete_rels(pubid, seqs, oldseqids));
/* And drop them. */
- PublicationDropTables(pubid, delrels, true);
+ PublicationDropRelations(pubid, delrels, true);
/*
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
+ PublicationAddRelations(pubid, seqs, true, stmt, RELKIND_SEQUENCE);
CloseRelationList(delrels);
}
CloseRelationList(rels);
+ CloseRelationList(seqs);
}
/*
@@ -1708,11 +1724,9 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
&excepttbls, &exceptseqs, &schemaidlist);
- /*
- * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
- * PUBLICATION.
- */
- Assert(exceptseqs == NIL);
+ /* EXCEPT clause is only supported for ALTER PUBLICATION ... SET */
+ Assert((excepttbls == NIL && exceptseqs == NIL) ||
+ stmt->action == AP_SetObjects);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1736,8 +1750,8 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
stmt->pubname));
relations = list_concat(relations, excepttbls);
- AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
- schemaidlist != NIL);
+ AlterPublicationRelations(stmt, tup, relations, exceptseqs,
+ pstate->p_sourcetext, schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
AlterPublicationAllFlags(stmt, rel, tup);
}
@@ -2092,10 +2106,10 @@ PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
}
/*
- * Remove listed tables from the publication.
+ * Remove listed relations from the publication.
*/
static void
-PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+PublicationDropRelations(Oid pubid, List *rels, bool missing_ok)
{
ObjectAddress obj;
ListCell *lc;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f0be22ed941..18577908de6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11499,7 +11499,7 @@ pub_except_seq_list: PublicationExceptSeqSpec
* pub_all_obj_type is one of:
*
* ALL TABLES [ EXCEPT ( TABLE table_name [, ...] ) ]
- * ALL SEQUENCES
+ * ALL SEQUENCES [ EXCEPT ( SEQUENCE sequence_name [, ...] ) ]
*
*****************************************************************************/
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 155087f572b..105adb87874 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2337,6 +2337,20 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "TABLES", "IN", "SCHEMA"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
" AND nspname NOT LIKE E'pg\\\\_%%'",
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 49c29a87630..21a1527bcc7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -178,9 +178,11 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
extern List *GetExcludedPublicationRelations(Oid pubid,
- PublicationPartOpt pub_partopt);
+ PublicationPartOpt pub_partopt,
+ char pubrelkind);
extern List *GetAllTablesPublications(void);
-extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
+extern List *GetAllPublicationRelations(Oid pubid, char pubrelkind,
+ bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
extern List *GetSchemaPublications(Oid schemaid);
extern List *GetSchemaPublicationRelations(Oid schemaid,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index a39ea881002..daf179af1df 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -577,6 +577,25 @@ Except sequences:
"pub_test.regress_seq2"
"public.regress_seq0"
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "public.regress_seq0"
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+(1 row)
+
-- Test combination of ALL SEQUENCES and ALL TABLES each with an EXCEPT clause
CREATE TABLE regress_tab1(a int);
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
@@ -596,24 +615,40 @@ CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0,
ERROR: syntax error at or near "regress_seq0"
LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
^
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ..._forallsequences_except SET ALL SEQUENCES EXCEPT (regress_se...
+ ^
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for unlogged sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for temporary sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for sequences.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
-- fail - table object is specified in EXCEPT sequence list
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
DETAIL: This operation is not supported for tables.
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index c3dd433e8da..26f5cb1bdf1 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -269,6 +269,14 @@ CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (
ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
\dRp+ regress_pub_forallsequences_except
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_forallsequences_except
+
+-- Clear the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES;
+\dRp+ regress_pub_forallsequences_except
+
-- Test combination of ALL SEQUENCES and ALL TABLES each with an EXCEPT clause
CREATE TABLE regress_tab1(a int);
CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
@@ -277,20 +285,25 @@ RESET client_min_messages;
-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (regress_seq0, pub_test.regress_seq1);
-- fail - unlogged sequence is specified in EXCEPT sequence list
CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
-- fail - temporary sequence is specified in EXCEPT sequence list
CREATE TEMPORARY SEQUENCE regress_seq_temp;
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
-- fail - sequence object is specified in EXCEPT table list
CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL TABLES EXCEPT (TABLE regress_seq0);
-- fail - table object is specified in EXCEPT sequence list
CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ALTER PUBLICATION regress_pub_forallsequences_except SET ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 94e553b2e89..5f29a0fe22a 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -289,6 +289,7 @@ $node_publisher->safe_psql(
'postgres', qq (
CREATE TABLE seq_test (v BIGINT);
CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub1_2;
CREATE SEQUENCE seq_excluded_in_pub2;
INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
@@ -297,6 +298,7 @@ $node_publisher->safe_psql(
$node_subscriber->safe_psql(
'postgres', qq(
CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub1_2;
CREATE SEQUENCE seq_excluded_in_pub2;
CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
));
@@ -316,6 +318,24 @@ $result = $node_subscriber->safe_psql('postgres',
"SELECT last_value, is_called FROM seq_excluded_in_pub2");
is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+# Check ALTER PUBLICATION ... ALL SEQUENCES EXCEPT (SEQUENCE ...)
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ ALTER PUBLICATION tap_pub_all_seq_except1 SET ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1, seq_excluded_in_pub1_2);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1_2') FROM generate_series(1,100);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH PUBLICATION;
+ ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES;
+));
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1_2");
+is($result, '1|f', 'sequences in the EXCEPT list are excluded');
+
# ============================================
# Test when a subscription is subscribing to multiple publications
# ============================================
--
2.34.1
[application/x-patch] v20-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch (67.5K, ../../CANhcyEVe8HdiXv-_8QtXBjwGmCrwCYkqA-adBZJh=QY4HWB6jQ@mail.gmail.com/3-v20-0001-Support-EXCEPT-for-ALL-SEQUENCES-in-CREATE-PUBLI.patch)
download | inline diff:
From cb40a6916c4f50df7f9255cb9988b90ad5835024 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <[email protected]>
Date: Tue, 26 May 2026 10:51:26 +0530
Subject: [PATCH v20 1/2] Support EXCEPT for ALL SEQUENCES in CREATE
PUBLICATION
Extend CREATE PUBLICATION ... FOR ALL SEQUENCES to support the
EXCEPT syntax. This allows one or more sequences to be excluded. The
publisher will not send the data of excluded sequences to the subscriber.
Example:
CREATE PUBLICATION pub1 FOR ALL SEQUENCES EXCEPT (SEQUENCE s1, s2);
---
doc/src/sgml/catalogs.sgml | 8 +-
doc/src/sgml/logical-replication.sgml | 8 +-
doc/src/sgml/ref/create_publication.sgml | 46 ++++++---
src/backend/catalog/pg_publication.c | 71 ++++++++-----
src/backend/commands/publicationcmds.c | 118 +++++++++++++--------
src/backend/parser/gram.y | 119 ++++++++++++++--------
src/bin/pg_dump/pg_dump.c | 65 +++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 22 ++++
src/bin/psql/describe.c | 97 ++++++++++++++++--
src/bin/psql/tab-complete.in.c | 12 +++
src/include/catalog/pg_publication.h | 7 +-
src/include/nodes/parsenodes.h | 19 ++--
src/test/regress/expected/publication.out | 99 ++++++++++++++++--
src/test/regress/sql/publication.sql | 52 +++++++++-
src/test/subscription/t/037_except.pl | 77 ++++++++++++++
src/tools/pgindent/typedefs.list | 2 +-
17 files changed, 644 insertions(+), 179 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..43a3566dda2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7099,7 +7099,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
</para>
<para>
- Reference to table
+ Reference to table or sequence
</para></entry>
</row>
@@ -7108,8 +7108,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prexcept</structfield> <type>bool</type>
</para>
<para>
- True if the table is excluded from the publication. See
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>.
+ True if the table or sequence is excluded from the publication. See
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>.
</para></entry>
</row>
@@ -7118,7 +7118,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>prqual</structfield> <type>pg_node_tree</type>
</para>
<para>Expression tree (in <function>nodeToString()</function>
- representation) for the relation's publication qualifying condition. Null
+ representation) for the table's publication qualifying condition. Null
if there is no publication qualifying condition.</para></entry>
</row>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 3b22ee229b9..66aabf730a5 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -118,8 +118,12 @@
synchronized at any time. For more information, see
<xref linkend="logical-replication-sequences"/>. When a publication is
created with <literal>FOR ALL TABLES</literal>, a table or set of tables can
- be explicitly excluded from publication using the
- <link linkend="sql-createpublication-params-for-except-table"><literal>EXCEPT</literal></link>
+ be explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
+ clause. Similarly, when a publication is created with
+ <literal>FOR ALL SEQUENCES</literal>, a sequence or set of sequences can be
+ explicitly excluded from the publication using the
+ <link linkend="sql-createpublication-params-for-except"><literal>EXCEPT</literal></link>
clause.
</para>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 85cfcaddafa..360dc7eda6a 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -33,7 +33,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">publication_all_object</replaceable> is one of:</phrase>
ALL TABLES [ EXCEPT ( <replaceable class="parameter">except_table_object</replaceable> [, ... ] ) ]
- ALL SEQUENCES
+ ALL SEQUENCES [ EXCEPT ( <replaceable class="parameter">except_sequence_object</replaceable> [, ... ] ) ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -46,6 +46,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<phrase>and <replaceable class="parameter">table_object</replaceable> is:</phrase>
[ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
+
+<phrase>and <replaceable class="parameter">except_sequence_object</replaceable> is:</phrase>
+
+ SEQUENCE <replaceable class="parameter">sequence_name</replaceable> [, ... ]
</synopsis>
</refsynopsisdiv>
@@ -189,22 +193,26 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
<para>
Only persistent sequences are included in the publication. Temporary
sequences and unlogged sequences are excluded from the publication.
+ Sequences listed in the <literal>EXCEPT</literal> clause are excluded from
+ the publication.
</para>
</listitem>
</varlistentry>
- <varlistentry id="sql-createpublication-params-for-except-table">
+ <varlistentry id="sql-createpublication-params-for-except">
<term><literal>EXCEPT</literal></term>
<listitem>
<para>
- This clause specifies a list of tables to be excluded from the
+ This clause specifies the tables or sequences to be excluded from an
+ <literal>ALL TABLES</literal> or <literal>ALL SEQUENCES</literal>
publication.
</para>
<para>
- Once a table is excluded, the exclusion applies to that table
- regardless of its name or schema. Renaming the table or moving it to
- another schema using <command>ALTER TABLE ... SET SCHEMA</command> does
- not remove the exclusion.
+ Once a table or sequence is excluded, the exclusion applies to that
+ object regardless of its name or schema. Renaming the object or moving
+ it to another schema using <command>ALTER TABLE ... SET SCHEMA</command>
+ or <command>ALTER SEQUENCE ... SET SCHEMA</command> does not remove the
+ exclusion.
</para>
<para>
For inherited tables, if <literal>ONLY</literal> is specified before the
@@ -223,9 +231,10 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
</para>
<para>
There can be a case where a subscription includes multiple publications.
- In such a case, a table or partition that is included in one publication
- but excluded (explicitly or implicitly) by the <literal>EXCEPT</literal>
- clause of another is considered included for replication.
+ In such a case, a table, partition or sequence that is included in one
+ publication but excluded (explicitly or implicitly) by the
+ <literal>EXCEPT</literal> clause of another is considered included for
+ replication.
</para>
</listitem>
</varlistentry>
@@ -553,11 +562,20 @@ CREATE PUBLICATION all_tables_except FOR ALL TABLES EXCEPT (TABLE users, departm
</para>
<para>
- Create a publication that publishes all sequences for synchronization, and
- all changes in all tables except <structname>users</structname> and
- <structname>departments</structname>:
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>:
+<programlisting>
+CREATE PUBLICATION all_sequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2);
+</programlisting>
+ </para>
+
+ <para>
+ Create a publication that publishes all sequences for synchronization except
+ <structname>seq1</structname> and <structname>seq2</structname>, and all
+ changes in all tables except
+ <structname>users</structname> and <structname>departments</structname>:
<programlisting>
-CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES, ALL TABLES EXCEPT (TABLE users, departments);
+CREATE PUBLICATION all_sequences_tables_except FOR ALL SEQUENCES EXCEPT (SEQUENCE seq1, seq2), ALL TABLES EXCEPT (TABLE users, departments);
</programlisting>
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 1ec94c851b2..1d14600255a 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -49,20 +49,28 @@ typedef struct
} published_rel;
/*
- * Check if relation can be in given publication and throws appropriate
- * error if not.
+ * Check if the target relation is allowed to be specified in the given
+ * publication and throw an error if not.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause. The relkind
+ * of the relation in 'pri' is checked for compatibility against it. Error is
+ * raised if they are not compatible.
*/
static void
-check_publication_add_relation(PublicationRelInfo *pri)
+check_publication_add_relation(PublicationRelInfo *pri, char pubrelkind)
{
Relation targetrel = pri->relation;
+ char targetrelkind = RelationGetForm(targetrel)->relkind;
const char *relname;
const char *errormsg;
if (pri->except)
{
relname = RelationGetQualifiedRelationName(targetrel);
- errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
+ if (pubrelkind == RELKIND_SEQUENCE)
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (SEQUENCE) clause");
+ else
+ errormsg = gettext_noop("cannot specify \"%s\" in the publication EXCEPT (TABLE) clause");
}
else
{
@@ -77,13 +85,23 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for individual partitions.")));
- /* Must be a regular or partitioned table */
- if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
- RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
+ /*
+ * Must be a regular or partitioned table when specified in FOR TABLE or
+ * EXCEPT table list
+ */
+ if (pubrelkind == RELKIND_RELATION && targetrelkind != RELKIND_RELATION &&
+ targetrelkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail_relkind_not_supported(targetrelkind)));
+
+ /* Must be a sequence if specified in EXCEPT sequence list */
+ if (pubrelkind == RELKIND_SEQUENCE && targetrelkind != RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
- errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
+ errdetail_relkind_not_supported(targetrelkind)));
/* Can't be system table */
if (IsCatalogRelation(targetrel))
@@ -104,11 +122,15 @@ check_publication_add_relation(PublicationRelInfo *pri)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for temporary sequences.") :
errdetail("This operation is not supported for temporary tables.")));
else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
+ targetrelkind == RELKIND_SEQUENCE ?
+ errdetail("This operation is not supported for unlogged sequences.") :
errdetail("This operation is not supported for unlogged tables.")));
}
@@ -531,7 +553,8 @@ attnumstoint2vector(Bitmapset *attrs)
*/
ObjectAddress
publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists, AlterPublicationStmt *alter_stmt)
+ bool if_not_exists, AlterPublicationStmt *alter_stmt,
+ char pubrelkind)
{
Relation rel;
HeapTuple tup;
@@ -569,7 +592,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
RelationGetRelationName(targetrel), pub->name)));
}
- check_publication_add_relation(pri);
+ check_publication_add_relation(pri, pubrelkind);
/* Validate and translate column names into a Bitmapset of attnums. */
attnums = pub_collist_validate(pri->relation, pri->columns);
@@ -989,15 +1012,17 @@ GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
}
/*
- * Gets list of table oids that were specified in the EXCEPT clause for a
- * publication.
- *
- * This should only be used FOR ALL TABLES publications.
+ * Gets list of relation oids that were specified in the EXCEPT clause for a
+ * 'FOR ALL TABLES' or a 'FOR ALL SEQUENCES' publication.
*/
List *
-GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
+GetExcludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
{
- Assert(GetPublication(pubid)->alltables);
+#ifdef USE_ASSERT_CHECKING
+ Publication *pub = GetPublication(pubid);
+
+ Assert(pub->alltables || pub->allsequences);
+#endif
return get_publication_relations(pubid, pub_partopt, true);
}
@@ -1048,8 +1073,10 @@ GetAllTablesPublications(void)
* root partitioned tables. This is not applicable to FOR ALL SEQUENCES
* publication.
*
- * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
- * in the EXCEPT clause.
+ * For a FOR ALL TABLES publication, the returned list excludes tables
+ * specified in the EXCEPT (TABLE ...) clause. For a FOR ALL SEQUENCES
+ * publication, it excludes sequences specified in the EXCEPT (SEQUENCE ...)
+ * clause.
*/
List *
GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
@@ -1063,11 +1090,9 @@ GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
- /* EXCEPT filtering applies only to relations, not sequences */
- if (relkind == RELKIND_RELATION)
- exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
- PUBLICATION_PART_ROOT :
- PUBLICATION_PART_LEAF);
+ exceptlist = GetExcludedPublicationRelations(pubid, pubviaroot ?
+ PUBLICATION_PART_ROOT :
+ PUBLICATION_PART_LEAF);
classRel = table_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..bd171f9e48e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -61,11 +61,11 @@ typedef struct rf_context
Oid parentid; /* relid of the parent relation */
} rf_context;
-static List *OpenTableList(List *tables);
-static void CloseTableList(List *rels);
+static List *OpenRelationList(List *tables);
+static void CloseRelationList(List *rels);
static void LockSchemaList(List *schemalist);
-static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt);
+static void PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind);
static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok);
static void PublicationAddSchemas(Oid pubid, List *schemas, bool if_not_exists,
AlterPublicationStmt *stmt);
@@ -176,12 +176,13 @@ parse_publication_options(ParseState *pstate,
}
/*
- * Convert the PublicationObjSpecType list into schema oid list and
- * PublicationTable list.
+ * Convert the PublicationObjSpecType list into PublicationRelation lists
+ * (`rels`, `excepttbls`, `exceptseqs`) and a schema oid list (`schemas`).
*/
static void
ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
- List **rels, List **exceptrels, List **schemas)
+ List **rels, List **excepttbls, List **exceptseqs,
+ List **schemas)
{
ListCell *cell;
PublicationObjSpec *pubobj;
@@ -199,12 +200,16 @@ ObjectsInPublicationToOids(List *pubobjspec_list, ParseState *pstate,
switch (pubobj->pubobjtype)
{
case PUBLICATIONOBJ_EXCEPT_TABLE:
- pubobj->pubtable->except = true;
- *exceptrels = lappend(*exceptrels, pubobj->pubtable);
+ pubobj->pubrelation->except = true;
+ *excepttbls = lappend(*excepttbls, pubobj->pubrelation);
+ break;
+ case PUBLICATIONOBJ_EXCEPT_SEQUENCE:
+ pubobj->pubrelation->except = true;
+ *exceptseqs = lappend(*exceptseqs, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLE:
- pubobj->pubtable->except = false;
- *rels = lappend(*rels, pubobj->pubtable);
+ pubobj->pubrelation->except = false;
+ *rels = lappend(*rels, pubobj->pubrelation);
break;
case PUBLICATIONOBJ_TABLES_IN_SCHEMA:
schemaid = get_namespace_oid(pubobj->name, false);
@@ -849,7 +854,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
char publish_generated_columns;
AclResult aclresult;
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
/* must have CREATE privilege on database */
@@ -936,18 +942,29 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
/* Associate objects with the publication. */
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ if (stmt->for_all_sequences)
+ {
+ /* Process EXCEPT sequence list */
+ if (exceptseqs != NIL)
+ {
+ List *rels = OpenRelationList(exceptseqs);
+
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_SEQUENCE);
+ CloseRelationList(rels);
+ }
+ }
if (stmt->for_all_tables)
{
/* Process EXCEPT table list */
- if (exceptrelations != NIL)
+ if (excepttbls != NIL)
{
- List *rels;
+ List *rels = OpenRelationList(excepttbls);
- rels = OpenTableList(exceptrelations);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
/*
@@ -969,7 +986,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- rels = OpenTableList(relations);
+ rels = OpenRelationList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -977,8 +994,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
schemaidlist != NIL,
publish_via_partition_root);
- PublicationAddTables(puboid, rels, true, NULL);
- CloseTableList(rels);
+ PublicationAddRelations(puboid, rels, true, NULL, RELKIND_RELATION);
+ CloseRelationList(rels);
}
if (schemaidlist != NIL)
@@ -1255,7 +1272,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (!tables && stmt->action != AP_SetObjects)
return;
- rels = OpenTableList(tables);
+ rels = OpenRelationList(tables);
if (stmt->action == AP_AddObjects)
{
@@ -1266,7 +1283,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
pubform->pubviaroot);
- PublicationAddTables(pubid, rels, false, stmt);
+ PublicationAddRelations(pubid, rels, false, stmt, RELKIND_RELATION);
}
else if (stmt->action == AP_DropObjects)
PublicationDropTables(pubid, rels, false);
@@ -1289,8 +1306,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
*/
if (pubform->puballtables)
{
- oldrelids = GetExcludedPublicationTables(pubid,
- PUBLICATION_PART_ROOT);
+ oldrelids = GetExcludedPublicationRelations(pubid,
+ PUBLICATION_PART_ROOT);
}
}
else
@@ -1363,8 +1380,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
/*
* Validate the column list. If the column list or WHERE
* clause changes, then the validation done here will be
- * duplicated inside PublicationAddTables(). The validation
- * is cheap enough that that seems harmless.
+ * duplicated inside PublicationAddRelations(). The
+ * validation is cheap enough that that seems harmless.
*/
newcolumns = pub_collist_validate(newpubrel->relation,
newpubrel->columns);
@@ -1410,12 +1427,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
* Don't bother calculating the difference for adding, we'll catch and
* skip existing ones when doing catalog update.
*/
- PublicationAddTables(pubid, rels, true, stmt);
+ PublicationAddRelations(pubid, rels, true, stmt, RELKIND_RELATION);
- CloseTableList(delrels);
+ CloseRelationList(delrels);
}
- CloseTableList(rels);
+ CloseRelationList(rels);
}
/*
@@ -1651,7 +1668,7 @@ AlterPublicationAllFlags(AlterPublicationStmt *stmt, Relation rel,
* Alter the existing publication.
*
* This is dispatcher function for AlterPublicationOptions,
- * AlterPublicationSchemas and AlterPublicationTables.
+ * AlterPublicationSchemas and AlterPublicationRelations.
*/
void
AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
@@ -1683,12 +1700,19 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
else
{
List *relations = NIL;
- List *exceptrelations = NIL;
+ List *excepttbls = NIL;
+ List *exceptseqs = NIL;
List *schemaidlist = NIL;
Oid pubid = pubform->oid;
ObjectsInPublicationToOids(stmt->pubobjects, pstate, &relations,
- &exceptrelations, &schemaidlist);
+ &excepttbls, &exceptseqs, &schemaidlist);
+
+ /*
+ * TODO: EXCEPT (SEQUENCE ...) is not yet supported with ALTER
+ * PUBLICATION.
+ */
+ Assert(exceptseqs == NIL);
CheckAlterPublication(stmt, tup, relations, schemaidlist);
@@ -1711,7 +1735,7 @@ AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt)
errmsg("publication \"%s\" does not exist",
stmt->pubname));
- relations = list_concat(relations, exceptrelations);
+ relations = list_concat(relations, excepttbls);
AlterPublicationTables(stmt, tup, relations, pstate->p_sourcetext,
schemaidlist != NIL);
AlterPublicationSchemas(stmt, tup, schemaidlist);
@@ -1830,12 +1854,12 @@ RemovePublicationSchemaById(Oid psoid)
}
/*
- * Open relations specified by a PublicationTable list.
- * The returned tables are locked in ShareUpdateExclusiveLock mode in order to
- * add them to a publication.
+ * Open relations specified by a PublicationRelation list.
+ * The returned relations are locked in ShareUpdateExclusiveLock mode in order
+ * to add them to a publication.
*/
static List *
-OpenTableList(List *tables)
+OpenRelationList(List *tables)
{
List *relids = NIL;
List *rels = NIL;
@@ -1848,7 +1872,7 @@ OpenTableList(List *tables)
*/
foreach(lc, tables)
{
- PublicationTable *t = lfirst_node(PublicationTable, lc);
+ PublicationRelation *t = lfirst_node(PublicationRelation, lc);
bool recurse = t->relation->inh;
Relation rel;
Oid myrelid;
@@ -1987,7 +2011,7 @@ OpenTableList(List *tables)
* Close all relations in the list.
*/
static void
-CloseTableList(List *rels)
+CloseRelationList(List *rels)
{
ListCell *lc;
@@ -2032,11 +2056,15 @@ LockSchemaList(List *schemalist)
}
/*
- * Add listed tables to the publication.
+ * Add listed relations to the publication.
+ *
+ * 'pubrelkind' is the relkind accepted by the publication clause.
+ * The relkind of each relation in 'rels' is checked for compatibility
+ * against it.
*/
static void
-PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
- AlterPublicationStmt *stmt)
+PublicationAddRelations(Oid pubid, List *rels, bool if_not_exists,
+ AlterPublicationStmt *stmt, char pubrelkind)
{
ListCell *lc;
@@ -2046,12 +2074,12 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
Relation rel = pub_rel->relation;
ObjectAddress obj;
- /* Must be owner of the table or superuser. */
+ /* Must be owner of the relation or superuser. */
if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
RelationGetRelationName(rel));
- obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt);
+ obj = publication_add_relation(pubid, pub_rel, if_not_exists, stmt, pubrelkind);
if (stmt)
{
EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..f0be22ed941 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -457,7 +457,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list pub_all_obj_type_list
- pub_except_obj_list opt_pub_except_clause
+ pub_except_tbl_list opt_pub_except_tbl_clause
+ pub_except_seq_list opt_pub_except_seq_clause
%type <retclause> returning_clause
%type <node> returning_option
@@ -597,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <publicationobjectspec> PublicationObjSpec
-%type <publicationobjectspec> PublicationExceptObjSpec
+%type <publicationobjectspec> PublicationExceptTblSpec
+%type <publicationobjectspec> PublicationExceptSeqSpec
%type <publicationallobjectspec> PublicationAllObjSpec
%type <keyword> unreserved_keyword type_func_name_keyword
@@ -11265,7 +11267,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
* pub_all_obj_type is one of:
*
* TABLES [EXCEPT (TABLE table [, ...] )]
- * SEQUENCES
+ * SEQUENCES [EXCEPT (SEQUENCE sequence [, ...] )]
*
* CREATE PUBLICATION FOR pub_obj [, ...] [WITH options]
*
@@ -11327,10 +11329,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $2;
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $2;
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
}
| TABLES IN_P SCHEMA ColId
{
@@ -11351,7 +11353,7 @@ PublicationObjSpec:
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
/*
* If either a row filter or column list is specified, create
- * a PublicationTable object.
+ * a PublicationRelation object.
*/
if ($2 || $3)
{
@@ -11361,10 +11363,10 @@ PublicationObjSpec:
* error will be thrown later via
* preprocess_pubobj_list().
*/
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVar(NULL, $1, @1);
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVar(NULL, $1, @1);
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
else
{
@@ -11376,10 +11378,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
- $$->pubtable->columns = $3;
- $$->pubtable->whereClause = $4;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = makeRangeVarFromQualifiedName($1, $2, @1, yyscanner);
+ $$->pubrelation->columns = $3;
+ $$->pubrelation->whereClause = $4;
$$->location = @1;
}
/* grammar like tablename * , ONLY tablename, ONLY ( tablename ) */
@@ -11387,10 +11389,10 @@ PublicationObjSpec:
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_CONTINUATION;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->relation = $1;
- $$->pubtable->columns = $2;
- $$->pubtable->whereClause = $3;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->relation = $1;
+ $$->pubrelation->columns = $2;
+ $$->pubrelation->whereClause = $3;
}
| CURRENT_SCHEMA
{
@@ -11406,23 +11408,29 @@ pub_obj_list: PublicationObjSpec
{ $$ = lappend($1, $3); }
;
-opt_pub_except_clause:
- EXCEPT '(' TABLE pub_except_obj_list ')' { $$ = $4; }
+opt_pub_except_tbl_clause:
+ EXCEPT '(' TABLE pub_except_tbl_list ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+opt_pub_except_seq_clause:
+ EXCEPT '(' SEQUENCE pub_except_seq_list ')' { $$ = $4; }
| /*EMPTY*/ { $$ = NIL; }
;
PublicationAllObjSpec:
- ALL TABLES opt_pub_except_clause
+ ALL TABLES opt_pub_except_tbl_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_TABLES;
- $$->except_tables = $3;
+ $$->except_relations = $3;
$$->location = @1;
}
- | ALL SEQUENCES
+ | ALL SEQUENCES opt_pub_except_seq_clause
{
$$ = makeNode(PublicationAllObjSpec);
$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
+ $$->except_relations = $3;
$$->location = @1;
}
;
@@ -11433,21 +11441,41 @@ pub_all_obj_type_list: PublicationAllObjSpec
{ $$ = lappend($1, $3); }
;
-PublicationExceptObjSpec:
+PublicationExceptTblSpec:
relation_expr
{
$$ = makeNode(PublicationObjSpec);
$$->pubobjtype = PUBLICATIONOBJ_EXCEPT_TABLE;
- $$->pubtable = makeNode(PublicationTable);
- $$->pubtable->except = true;
- $$->pubtable->relation = $1;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
+ $$->location = @1;
+ }
+ ;
+
+PublicationExceptSeqSpec:
+ qualified_name
+ {
+ $$ = makeNode(PublicationObjSpec);
+ $$->pubobjtype = PUBLICATIONOBJ_EXCEPT_SEQUENCE;
+ $$->pubrelation = makeNode(PublicationRelation);
+ $$->pubrelation->except = true;
+ $$->pubrelation->relation = $1;
$$->location = @1;
}
;
-pub_except_obj_list: PublicationExceptObjSpec
+pub_except_tbl_list: PublicationExceptTblSpec
+ { $$ = list_make1($1); }
+ | pub_except_tbl_list ',' opt_table PublicationExceptTblSpec
+ { $$ = lappend($1, $4); }
+ ;
+
+pub_except_seq_list: PublicationExceptSeqSpec
{ $$ = list_make1($1); }
- | pub_except_obj_list ',' opt_table PublicationExceptObjSpec
+ | pub_except_seq_list ',' PublicationExceptSeqSpec
+ { $$ = lappend($1, $3); }
+ | pub_except_seq_list ',' SEQUENCE PublicationExceptSeqSpec
{ $$ = lappend($1, $4); }
;
@@ -20747,12 +20775,12 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
bool *all_tables, bool *all_sequences,
core_yyscan_t yyscanner)
{
- if (!all_objects_list)
- return;
-
*all_tables = false;
*all_sequences = false;
+ if (!all_objects_list)
+ return;
+
foreach_ptr(PublicationAllObjSpec, obj, all_objects_list)
{
if (obj->pubobjtype == PUBLICATION_ALL_TABLES)
@@ -20765,7 +20793,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_tables = true;
- *pubobjects = list_concat(*pubobjects, obj->except_tables);
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
else if (obj->pubobjtype == PUBLICATION_ALL_SEQUENCES)
{
@@ -20777,6 +20805,7 @@ preprocess_pub_all_objtype_list(List *all_objects_list, List **pubobjects,
parser_errposition(obj->location));
*all_sequences = true;
+ *pubobjects = list_concat(*pubobjects, obj->except_relations);
}
}
}
@@ -20812,8 +20841,8 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->pubobjtype == PUBLICATIONOBJ_TABLE)
{
- /* relation name or pubtable must be set for this type of object */
- if (!pubobj->name && !pubobj->pubtable)
+ /* Relation name or pubrelation must be set for this type of object */
+ if (!pubobj->name && !pubobj->pubrelation)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid table name"),
@@ -20821,12 +20850,12 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
if (pubobj->name)
{
- /* convert it to PublicationTable */
- PublicationTable *pubtable = makeNode(PublicationTable);
+ /* Convert it to PublicationRelation */
+ PublicationRelation *pubrelation = makeNode(PublicationRelation);
- pubtable->relation =
+ pubrelation->relation =
makeRangeVar(NULL, pubobj->name, pubobj->location);
- pubobj->pubtable = pubtable;
+ pubobj->pubrelation = pubrelation;
pubobj->name = NULL;
}
}
@@ -20834,14 +20863,14 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
pubobj->pubobjtype == PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA)
{
/* WHERE clause is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->whereClause)
+ if (pubobj->pubrelation && pubobj->pubrelation->whereClause)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WHERE clause not allowed for schema"),
parser_errposition(pubobj->location));
/* Column list is not allowed on a schema object */
- if (pubobj->pubtable && pubobj->pubtable->columns)
+ if (pubobj->pubrelation && pubobj->pubrelation->columns)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column specification not allowed for schema"),
@@ -20849,11 +20878,11 @@ preprocess_pubobj_list(List *pubobjspec_list, core_yyscan_t yyscanner)
/*
* We can distinguish between the different type of schema objects
- * based on whether name and pubtable is set.
+ * based on whether name and pubrelation is set.
*/
if (pubobj->name)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_SCHEMA;
- else if (!pubobj->name && !pubobj->pubtable)
+ else if (!pubobj->name && !pubobj->pubrelation)
pubobj->pubobjtype = PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA;
else
ereport(ERROR,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4d660d14b4c..526e211c84e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4571,23 +4571,31 @@ getPublications(Archive *fout)
{
NULL, NULL
};
+ pubinfo[i].except_sequences = (SimplePtrList)
+ {
+ NULL, NULL
+ };
/* Decide whether we want to dump it */
selectDumpableObject(&(pubinfo[i].dobj), fout);
/*
- * Get the list of tables for publications specified in the EXCEPT
- * TABLE clause.
+ * Get the list of tables and sequences explicitly excluded from the
+ * publication.
*
- * Although individual table entries in EXCEPT list could be stored in
- * PublicationRelInfo, dumpPublicationTable cannot be used to emit
- * them, because there is no ALTER PUBLICATION ... ADD command to add
- * individual table entries to the EXCEPT list.
+ * Although individual table/sequence entries in EXCEPT list could be
+ * stored in PublicationRelInfo, dumpPublicationTable cannot be used
+ * to emit them, because there is no ALTER PUBLICATION ... ADD command
+ * to add individual table entries to the EXCEPT list.
*
* Therefore, the approach is to dump the complete EXCEPT list in a
* single CREATE PUBLICATION statement. PublicationInfo is used to
* collect this information, which is then emitted by
* dumpPublication().
+ *
+ * Note: EXCEPT (TABLE ...) was introduced in PG19, whereas EXCEPT
+ * (SEQUENCE ...) was introduced in PG20. Hence, the version checks
+ * below differ.
*/
if (fout->remoteVersion >= 190000)
{
@@ -4596,9 +4604,10 @@ getPublications(Archive *fout)
resetPQExpBuffer(query);
appendPQExpBuffer(query,
- "SELECT prrelid\n"
- "FROM pg_catalog.pg_publication_rel\n"
- "WHERE prpubid = %u AND prexcept",
+ "SELECT pr.prrelid, pc.relkind\n"
+ "FROM pg_catalog.pg_publication_rel pr\n"
+ "JOIN pg_catalog.pg_class pc ON pr.prrelid = pc.oid\n"
+ "WHERE pr.prpubid = %u AND pr.prexcept",
pubinfo[i].dobj.catId.oid);
res_tbls = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4608,14 +4617,26 @@ getPublications(Archive *fout)
for (int j = 0; j < ntbls; j++)
{
Oid prrelid;
+ char relkind;
TableInfo *tbinfo;
prrelid = atooid(PQgetvalue(res_tbls, j, 0));
+ relkind = *PQgetvalue(res_tbls, j, 1);
tbinfo = findTableByOid(prrelid);
if (tbinfo != NULL)
- simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ {
+ /* EXCEPT (SEQUENCE ...) is supported from PG20 onwards. */
+ if (fout->remoteVersion >= 200000 &&
+ relkind == RELKIND_SEQUENCE)
+ simple_ptr_list_append(&pubinfo[i].except_sequences, tbinfo);
+ else if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ simple_ptr_list_append(&pubinfo[i].except_tables, tbinfo);
+ else
+ Assert(false);
+ }
}
PQclear(res_tbls);
@@ -4675,12 +4696,30 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
}
if (n_except > 0)
appendPQExpBufferChar(query, ')');
+ }
+ if (pubinfo->puballsequences)
+ {
+ int n_except = 0;
- if (pubinfo->puballsequences)
+ if (pubinfo->puballtables)
appendPQExpBufferStr(query, ", ALL SEQUENCES");
+ else
+ appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
+
+ /* Include EXCEPT (SEQUENCE) clause if there are except_sequences. */
+ for (SimplePtrListCell *cell = pubinfo->except_sequences.head; cell; cell = cell->next)
+ {
+ TableInfo *tbinfo = (TableInfo *) cell->ptr;
+ const char *seqname = fmtQualifiedDumpable(tbinfo);
+
+ if (++n_except == 1)
+ appendPQExpBuffer(query, " EXCEPT (SEQUENCE %s", seqname);
+ else
+ appendPQExpBuffer(query, ", %s", seqname);
+ }
+ if (n_except > 0)
+ appendPQExpBufferChar(query, ')');
}
- else if (pubinfo->puballsequences)
- appendPQExpBufferStr(query, " FOR ALL SEQUENCES");
appendPQExpBufferStr(query, " WITH (publish = '");
if (pubinfo->pubinsert)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..8e869fe791a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -679,6 +679,7 @@ typedef struct _PublicationInfo
bool pubviaroot;
PublishGencolsType pubgencols_type;
SimplePtrList except_tables;
+ SimplePtrList except_sequences;
} PublicationInfo;
/*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..9a0673575d2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3283,6 +3283,26 @@ my %tests = (
like => { %full_runs, section_post_data => 1, },
},
+ 'CREATE PUBLICATION pub11' => {
+ create_order => 92,
+ create_sql => 'CREATE SEQUENCE test_except_seq;
+ CREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub11 FOR ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq, public.test_except_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
+ 'CREATE PUBLICATION pub12' => {
+ create_order => 92,
+ create_sql =>
+ 'CREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq);',
+ regexp => qr/^
+ \QCREATE PUBLICATION pub12 FOR ALL TABLES EXCEPT (TABLE ONLY dump_test.test_table), ALL SEQUENCES EXCEPT (SEQUENCE public.test_table_col1_seq) WITH (publish = 'insert, update, delete, truncate');\E
+ /xm,
+ like => { %full_runs, section_post_data => 1, },
+ },
+
'CREATE SUBSCRIPTION sub1' => {
create_order => 50,
create_sql => 'CREATE SUBSCRIPTION sub1
@@ -4082,6 +4102,8 @@ my %tests = (
},
'CREATE SEQUENCE test_table_col1_seq' => {
+ create_order => 90,
+ create_sql => 'CREATE SEQUENCE test_table_col1_seq',
regexp => qr/^
\QCREATE SEQUENCE dump_test.test_table_col1_seq\E
\n\s+\QAS integer\E
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index fef86b4cca3..1426111302b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1672,7 +1672,7 @@ describeOneTableDetails(const char *schemaname,
{
PGresult *result = NULL;
printQueryOpt myopt = pset.popt;
- char *footers[3] = {NULL, NULL, NULL};
+ char *footers[4] = {NULL, NULL, NULL, NULL};
printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information"));
appendPQExpBuffer(&buf,
@@ -1750,12 +1750,28 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get publications containing this sequence"));
- appendPQExpBuffer(&buf, "SELECT pubname FROM pg_catalog.pg_publication p"
+ appendPQExpBuffer(&buf, "SELECT p.pubname FROM pg_catalog.pg_publication p"
"\nWHERE p.puballsequences"
- "\n AND pg_catalog.pg_relation_is_publishable('%s')"
- "\nORDER BY 1",
+ "\n AND pg_catalog.pg_relation_is_publishable('%s')",
oid);
+ /*
+ * Skip entries where this sequence appears in the publication's
+ * EXCEPT list.
+ */
+ if (pset.sversion >= 200000)
+ {
+ appendPQExpBuffer(&buf,
+ "\n AND NOT EXISTS ("
+ "\n SELECT 1"
+ "\n FROM pg_catalog.pg_publication_rel pr"
+ "\n WHERE pr.prpubid = p.oid AND"
+ "\n pr.prrelid = '%s' AND pr.prexcept)",
+ oid);
+ }
+
+ appendPQExpBuffer(&buf, "\nORDER BY 1");
+
result = PSQLexec(buf.data);
if (result)
{
@@ -1780,6 +1796,42 @@ describeOneTableDetails(const char *schemaname,
}
}
+ /* Print publications that explicitly exclude this sequence */
+ if (pset.sversion >= 200000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT p.pubname\n"
+ "FROM pg_catalog.pg_publication p\n"
+ "JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid\n"
+ "WHERE pr.prrelid = '%s' AND pr.prexcept\n"
+ "ORDER BY 1;", oid);
+
+ result = PSQLexec(buf.data);
+ if (result)
+ {
+ int tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printfPQExpBuffer(&tmpbuf, _("Excluded from publications:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ appendPQExpBuffer(&tmpbuf, "\n \"%s\"", PQgetvalue(result, i, 0));
+
+ if (footers[0] == NULL)
+ footers[0] = pg_strdup(tmpbuf.data);
+ else if (footers[1] == NULL)
+ footers[1] = pg_strdup(tmpbuf.data);
+ else
+ footers[2] = pg_strdup(tmpbuf.data);
+ resetPQExpBuffer(&tmpbuf);
+ }
+
+ PQclear(result);
+ }
+ }
+
if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED)
printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""),
schemaname, relationname);
@@ -1796,6 +1848,7 @@ describeOneTableDetails(const char *schemaname,
pg_free(footers[0]);
pg_free(footers[1]);
+ pg_free(footers[2]);
retval = true;
goto error_return; /* not an error, just return early */
@@ -6727,6 +6780,7 @@ describePublications(const char *pattern)
char *pubid = PQgetvalue(res, i, 0);
char *pubname = PQgetvalue(res, i, 1);
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
+ bool puballsequences = strcmp(PQgetvalue(res, i, 4), "t") == 0;
printTableOpt myopt = pset.popt.topt;
initPQExpBuffer(&title);
@@ -6763,7 +6817,7 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
- if (!puballtables)
+ if (!puballtables && !puballsequences)
{
/* Get the tables for the specified publication */
printfPQExpBuffer(&buf, "/* %s */\n",
@@ -6817,11 +6871,15 @@ describePublications(const char *pattern)
goto error_return;
}
}
- else
+
+ if (puballtables)
{
if (pset.sversion >= 190000)
{
- /* Get tables in the EXCEPT clause for this publication */
+ /*
+ * Get tables in the EXCEPT (TABLE ...) clause for this
+ * publication
+ */
printfPQExpBuffer(&buf, "/* %s */\n",
_("Get tables excluded by this publication"));
appendPQExpBuffer(&buf,
@@ -6829,7 +6887,7 @@ describePublications(const char *pattern)
"FROM pg_catalog.pg_class c\n"
" JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
" JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
- "WHERE pr.prpubid = '%s' AND pr.prexcept\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind IN ('r','p')\n"
"ORDER BY 1", pubid);
if (!addFooterToPublicationDesc(&buf, _("Except tables:"),
true, &cont))
@@ -6837,6 +6895,29 @@ describePublications(const char *pattern)
}
}
+ if (puballsequences)
+ {
+ if (pset.sversion >= 200000)
+ {
+ /*
+ * Get sequences in the EXCEPT (SEQUENCE ...) clause for this
+ * publication
+ */
+ printfPQExpBuffer(&buf, "/* %s */\n",
+ _("Get sequences excluded by this publication"));
+ appendPQExpBuffer(&buf,
+ "SELECT n.nspname || '.' || c.relname\n"
+ "FROM pg_catalog.pg_class c\n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+ " JOIN pg_catalog.pg_publication_rel pr ON c.oid = pr.prrelid\n"
+ "WHERE pr.prpubid = '%s' AND pr.prexcept AND c.relkind = 'S'\n"
+ "ORDER BY 1", pubid);
+ if (!addFooterToPublicationDesc(&buf, _("Except sequences:"),
+ true, &cont))
+ goto error_return;
+ }
+ }
+
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 49ea584cd4f..155087f572b 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3748,6 +3748,18 @@ match_previous_words(int pattern_id,
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "EXCEPT", "(", "TABLE", MatchAnyN) && !ends_with(prev_wd, ','))
COMPLETE_WITH(")");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES"))
+ COMPLETE_WITH("EXCEPT ( SEQUENCE", "WITH (");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT"))
+ COMPLETE_WITH("( SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "("))
+ COMPLETE_WITH("SEQUENCE");
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && ends_with(prev_wd, ','))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "SEQUENCES", "EXCEPT", "(", "SEQUENCE", MatchAnyN) && !ends_with(prev_wd, ','))
+ COMPLETE_WITH(")");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES"))
COMPLETE_WITH("IN SCHEMA");
else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE", MatchAny) && !ends_with(prev_wd, ','))
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 89b4bb14f62..49c29a87630 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -177,8 +177,8 @@ typedef enum PublicationPartOpt
extern List *GetIncludedPublicationRelations(Oid pubid,
PublicationPartOpt pub_partopt);
-extern List *GetExcludedPublicationTables(Oid pubid,
- PublicationPartOpt pub_partopt);
+extern List *GetExcludedPublicationRelations(Oid pubid,
+ PublicationPartOpt pub_partopt);
extern List *GetAllTablesPublications(void);
extern List *GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot);
extern List *GetPublicationSchemas(Oid pubid);
@@ -200,7 +200,8 @@ extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
MemoryContext mcxt, Bitmapset **cols);
extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
bool if_not_exists,
- AlterPublicationStmt *alter_stmt);
+ AlterPublicationStmt *alter_stmt,
+ char pubrelkind);
extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
bool if_not_exists);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e03556399ab..eaa61bc72dd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4461,14 +4461,14 @@ typedef struct AlterTSConfigurationStmt
bool missing_ok; /* for DROP - skip error if missing? */
} AlterTSConfigurationStmt;
-typedef struct PublicationTable
+typedef struct PublicationRelation
{
NodeTag type;
- RangeVar *relation; /* publication relation */
- Node *whereClause; /* qualifications */
+ RangeVar *relation; /* publication table/sequence */
+ Node *whereClause; /* qualifications for publication table */
List *columns; /* List of columns in a publication table */
- bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+ bool except; /* True if listed in an EXCEPT clause */
+} PublicationRelation;
/*
* Publication object type
@@ -4476,7 +4476,8 @@ typedef struct PublicationTable
typedef enum PublicationObjSpecType
{
PUBLICATIONOBJ_TABLE, /* A table */
- PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in the EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_TABLE, /* A table in an EXCEPT clause */
+ PUBLICATIONOBJ_EXCEPT_SEQUENCE, /* A sequence in an EXCEPT clause */
PUBLICATIONOBJ_TABLES_IN_SCHEMA, /* All tables in schema */
PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA, /* All tables in first element of
* search_path */
@@ -4488,7 +4489,7 @@ typedef struct PublicationObjSpec
NodeTag type;
PublicationObjSpecType pubobjtype; /* type of this publication object */
char *name;
- PublicationTable *pubtable;
+ PublicationRelation *pubrelation;
ParseLoc location; /* token location, or -1 if unknown */
} PublicationObjSpec;
@@ -4505,7 +4506,9 @@ typedef struct PublicationAllObjSpec
{
NodeTag type;
PublicationAllObjType pubobjtype; /* type of this publication object */
- List *except_tables; /* tables specified in the EXCEPT clause */
+ List *except_relations; /* depending on the 'pubobjtype', this is
+ * a list of either tables or sequences
+ * specified in an EXCEPT clause */
ParseLoc location; /* token location, or -1 if unknown */
} PublicationAllObjSpec;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 29e54b214a0..a39ea881002 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -458,7 +458,7 @@ Excluded from publications:
Number of partitions: 1 (Use \d+ to list them.)
CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT (TABLE testpub_part1);
-ERROR: cannot specify relation "public.testpub_part1" in the publication EXCEPT clause
+ERROR: cannot specify "public.testpub_part1" in the publication EXCEPT (TABLE) clause
DETAIL: This operation is not supported for individual partitions.
CREATE TABLE tab_main (a int) PARTITION BY RANGE(a);
-- Attaching a partition is not allowed if the partitioned table appears in a
@@ -471,8 +471,9 @@ RESET client_min_messages;
DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
@@ -483,8 +484,8 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_pub_forallsequences1 | f | t
(1 row)
-\d+ regress_pub_seq0
- Sequence "public.regress_pub_seq0"
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -502,8 +503,8 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
- Sequence "pub_test.regress_pub_seq1"
+\d+ pub_test.regress_seq1
+ Sequence "pub_test.regress_seq1"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
@@ -534,10 +535,92 @@ SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname
regress_publication_user | t | t | t | f | f | f | stored | f |
(1 row)
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "public.regress_seq0"
+ "public.regress_seq2"
+
+-- Check that the sequence description shows the publications where it is listed
+-- in an EXCEPT clause
+\d+ regress_seq0
+ Sequence "public.regress_seq0"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Included in publications:
+ "regress_pub_for_allsequences_alltables"
+ "regress_pub_forallsequences1"
+ "regress_pub_forallsequences2"
+Excluded from publications:
+ "regress_pub_forallsequences_except"
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+ Publication regress_pub_forallsequences_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | f | t | t | t | t | t | none | f |
+Except sequences:
+ "pub_test.regress_seq1"
+ "pub_test.regress_seq2"
+ "public.regress_seq0"
+
+-- Test combination of ALL SEQUENCES and ALL TABLES each with an EXCEPT clause
+CREATE TABLE regress_tab1(a int);
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+ Publication regress_pub_for_allsequences_alltables_except
+ Owner | All tables | All sequences | Inserts | Updates | Deletes | Truncates | Generated columns | Via root | Description
+--------------------------+------------+---------------+---------+---------+---------+-----------+-------------------+----------+-------------
+ regress_publication_user | t | t | t | t | t | t | none | f |
+Except tables:
+ "public.regress_tab1"
+Except sequences:
+ "public.regress_seq0"
+
+RESET client_min_messages;
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+ERROR: syntax error at or near "regress_seq0"
+LINE 1: ...ON regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_se...
+ ^
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+ERROR: cannot specify "public.regress_seq_unlogged" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for unlogged sequences.
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+ERROR: cannot specify "pg_temp.regress_seq_temp" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for temporary sequences.
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+ERROR: cannot specify "public.regress_seq0" in the publication EXCEPT (TABLE) clause
+DETAIL: This operation is not supported for sequences.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+ERROR: cannot specify "public.regress_tab1" in the publication EXCEPT (SEQUENCE) clause
+DETAIL: This operation is not supported for tables.
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE regress_tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
ERROR: invalid publication object list
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 041e14a4de6..c3dd433e8da 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -221,8 +221,9 @@ DROP TABLE testpub_root, testpub_part1, tab_main;
DROP PUBLICATION testpub8;
--- Tests for publications with SEQUENCES
-CREATE SEQUENCE regress_pub_seq0;
-CREATE SEQUENCE pub_test.regress_pub_seq1;
+CREATE SEQUENCE regress_seq0;
+CREATE SEQUENCE pub_test.regress_seq1;
+CREATE SEQUENCE regress_seq2;
-- FOR ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -230,7 +231,7 @@ CREATE PUBLICATION regress_pub_forallsequences1 FOR ALL SEQUENCES;
RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_forallsequences1';
-\d+ regress_pub_seq0
+\d+ regress_seq0
\dRp+ regress_pub_forallsequences1
SET client_min_messages = 'ERROR';
@@ -238,7 +239,7 @@ CREATE PUBLICATION regress_pub_forallsequences2 FOR ALL SEQUENCES;
RESET client_min_messages;
-- check that describe sequence lists both publications the sequence belongs to
-\d+ pub_test.regress_pub_seq1
+\d+ pub_test.regress_seq1
--- Specifying both ALL TABLES and ALL SEQUENCES
SET client_min_messages = 'ERROR';
@@ -253,10 +254,51 @@ RESET client_min_messages;
SELECT pubname, puballtables, puballsequences FROM pg_publication WHERE pubname = 'regress_pub_for_allsequences_alltables';
\dRp+ regress_pub_for_allsequences_alltables
-DROP SEQUENCE regress_pub_seq0, pub_test.regress_pub_seq1;
+---------------------------------------------
+-- EXCEPT clause tests for sequences
+---------------------------------------------
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_forallsequences_except FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0, pub_test.regress_seq1, SEQUENCE regress_seq2);
+\dRp+ regress_pub_forallsequences_except
+-- Check that the sequence description shows the publications where it is listed
+-- in an EXCEPT clause
+\d+ regress_seq0
+
+-- Verify that an excluded sequence remains excluded after being moved to
+-- another schema.
+ALTER SEQUENCE regress_seq2 SET SCHEMA pub_test;
+\dRp+ regress_pub_forallsequences_except
+
+-- Test combination of ALL SEQUENCES and ALL TABLES each with an EXCEPT clause
+CREATE TABLE regress_tab1(a int);
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR ALL TABLES EXCEPT (TABLE regress_tab1), ALL SEQUENCES EXCEPT (SEQUENCE regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+RESET client_min_messages;
+
+-- fail - first sequence in the EXCEPT list should use SEQUENCE keyword
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (regress_seq0, pub_test.regress_seq1);
+
+-- fail - unlogged sequence is specified in EXCEPT sequence list
+CREATE UNLOGGED SEQUENCE regress_seq_unlogged;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_unlogged);
+
+-- fail - temporary sequence is specified in EXCEPT sequence list
+CREATE TEMPORARY SEQUENCE regress_seq_temp;
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_seq_temp);
+
+-- fail - sequence object is specified in EXCEPT table list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL TABLES EXCEPT (TABLE regress_seq0);
+
+-- fail - table object is specified in EXCEPT sequence list
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT (SEQUENCE regress_tab1);
+
+DROP SEQUENCE regress_seq0, pub_test.regress_seq1, pub_test.regress_seq2, regress_seq_unlogged, regress_seq_temp;
DROP PUBLICATION regress_pub_forallsequences1;
DROP PUBLICATION regress_pub_forallsequences2;
+DROP PUBLICATION regress_pub_forallsequences_except;
DROP PUBLICATION regress_pub_for_allsequences_alltables;
+DROP PUBLICATION regress_pub_for_allsequences_alltables_except;
+DROP TABLE regress_tab1;
-- fail - Specifying ALL TABLES more than once
CREATE PUBLICATION regress_pub_for_allsequences_alltables FOR ALL SEQUENCES, ALL TABLES, ALL TABLES;
diff --git a/src/test/subscription/t/037_except.pl b/src/test/subscription/t/037_except.pl
index 8c58d282eee..94e553b2e89 100644
--- a/src/test/subscription/t/037_except.pl
+++ b/src/test/subscription/t/037_except.pl
@@ -282,6 +282,83 @@ $node_subscriber->safe_psql('postgres', 'DROP SUBSCRIPTION tap_sub');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub1');
$node_publisher->safe_psql('postgres', 'DROP PUBLICATION tap_pub2');
+# ============================================
+# EXCEPT clause test cases for sequences
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq (
+ CREATE TABLE seq_test (v BIGINT);
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except1 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub1);
+));
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ CREATE SEQUENCE seq_excluded_in_pub1;
+ CREATE SEQUENCE seq_excluded_in_pub2;
+ CREATE SUBSCRIPTION tap_sub_all_seq_except CONNECTION '$publisher_connstr' PUBLICATION tap_pub_all_seq_except1;
+));
+
+# Wait for initial sync to finish
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check the initial data on subscriber
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '1|f', 'sequences in the EXCEPT list are excluded');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '100|t', 'initial test data replicated for seq_excluded_in_pub2');
+
+# ============================================
+# Test when a subscription is subscribing to multiple publications
+# ============================================
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub1') FROM generate_series(1,100);
+ INSERT INTO seq_test SELECT nextval('seq_excluded_in_pub2') FROM generate_series(1,100);
+ CREATE PUBLICATION tap_pub_all_seq_except2 FOR ALL SEQUENCES EXCEPT (SEQUENCE seq_excluded_in_pub2);
+));
+
+# Subscribe to multiple publications with different EXCEPT sequence lists
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION tap_sub_all_seq_except SET PUBLICATION tap_pub_all_seq_except1, tap_pub_all_seq_except2;
+ ALTER SUBSCRIPTION tap_sub_all_seq_except REFRESH SEQUENCES;
+));
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# seq_excluded_in_pub1 is excluded in tap_pub_all_seq_except1 but included in
+# tap_pub_all_seq_except2, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub1");
+is($result, '200|t',
+ 'check replication of a sequence excluded by one publication but included by another'
+);
+
+# seq_excluded_in_pub2 is excluded in tap_pub_all_seq_except2 but included in
+# tap_pub_all_seq_except1, so overall the subscription treats it as included.
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM seq_excluded_in_pub2");
+is($result, '200|t',
+ 'check replication of a sequence excluded by one publication but included by another'
+);
+
+# Cleanup
+$node_subscriber->safe_psql('postgres',
+ 'DROP SUBSCRIPTION tap_sub_all_seq_except');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except1');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except2');
+
$node_publisher->stop('fast');
done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ffb413ab612..feacd880bd9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2475,8 +2475,8 @@ PublicationObjSpecType
PublicationPartOpt
PublicationRelInfo
PublicationRelKind
+PublicationRelation
PublicationSchemaInfo
-PublicationTable
PublishGencolsType
PullFilter
PullFilterOps
--
2.34.1
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-30 23:49 ` Peter Smith <[email protected]>
1 sibling, 0 replies; 99+ messages in thread
From: Peter Smith @ 2026-06-30 23:49 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Shlok.
Some review comments for v15.
//////
v15-0001
//////
======
GENERAL
1.
The "20devel" is available, so now you can modify those version checks
in pg_dump.c and describe.c code.
======
src/test/regress/sql/publication.sql
2.
+-- fail - table object is specified in EXCEPT sequence list
+CREATE TABLE tab1(a int);
+CREATE PUBLICATION regress_pub_should_fail FOR ALL SEQUENCES EXCEPT
(SEQUENCE tab1);
All the other objects have a `regress_` prefix so should this be `regress_tab1`?
~~~
3.
+-- Test combination of ALL SEQUENCES and ALL TABLES with EXCEPT clause
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION regress_pub_for_allsequences_alltables_except FOR
ALL TABLES EXCEPT (TABLE testpub_tbl1), ALL SEQUENCES EXCEPT (SEQUENCE
regress_seq0);
+\dRp+ regress_pub_for_allsequences_alltables_except
+RESET client_min_messages;
3a.
I suggest moving this test to be grouped with the earlier non-failing
tests. Probably then you won't need the extra set/reset
client_min_messages.
~
3b.
Maybe you could have excluded the same `tab1` (renamed `regress_tab1`)
that was created, instead of excluding some random table from
elsewhere.
======
src/test/subscription/t/037_except.pl
4.
+$node_subscriber->safe_psql('postgres',
+ 'DROP SUBSCRIPTION tap_sub_all_seq_except');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except1');
+$node_publisher->safe_psql('postgres',
+ 'DROP PUBLICATION tap_pub_all_seq_except2');
+
$node_publisher->stop('fast');
Missing a comment that says "Cleanup" ?
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-29 10:32 ` shveta malik <[email protected]>
1 sibling, 0 replies; 99+ messages in thread
From: shveta malik @ 2026-06-29 10:32 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>; Peter Smith <[email protected]>; shveta malik <[email protected]>
On Fri, Jun 26, 2026 at 6:08 PM Shlok Kyal <[email protected]> wrote:
>
> I have addressed all the comments. I have also addressed the comments
> by Peter in [1].
> Please find the updated v14 patch.
>
> [1]: https://www.postgresql.org/message-id/CAHut%2BPsgCQa2hcT8TNqejV3y4U5ouj%3DZCOLMxgVGvBrUEkLaKg%40mail...
Thanks, a few trivial comments:
001:
1)
PublicationRelation
+typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication relation */
Node *whereClause; /* qualifications */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
-} PublicationTable;
+} PublicationRelation;
Can we make comments more clear. Suggestion:
typedef struct PublicationRelation
{
NodeTag type;
RangeVar *relation; /* publication table/sequence */
Node *whereClause; /* qualifications for publication table */
List *columns; /* List of columns in a publication table */
bool except; /* True if listed in the EXCEPT clause */
} PublicationRelation;
002:
2)
+ if ((pubrelkind == RELKIND_RELATION && relkind == RELKIND_RELATION) ||
+ (pubrelkind == RELKIND_RELATION && relkind == RELKIND_PARTITIONED_TABLE) ||
+ (pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
Can we optimize it to:
if ((pubrelkind == RELKIND_RELATION &&
(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) ||
(pubrelkind == RELKIND_SEQUENCE && relkind == RELKIND_SEQUENCE))
3)
+-- Modify the sequence list in the EXCEPT clause
+ALTER PUBLICATION regress_pub_forallsequences3 SET ALL SEQUENCES
EXCEPT (SEQUENCE regress_pub_seq0);
After this or at the end, can we add a testcase for 'SET ALL
SEQUENCES' to cover the reset except-seq code-flow.
thanks
Shveta
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-23 11:39 ` solai v <[email protected]>
1 sibling, 0 replies; 99+ messages in thread
From: solai v @ 2026-06-23 11:39 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: Peter Smith <[email protected]>; shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi Shlok,
I applied the latest v11 patch series and tested it on my setup.
I verified the new EXCEPT support for both CREATE PUBLICATION and
ALTER PUBLICATION with FOR ALL SEQUENCES. I tested single and multiple
sequence exclusions, replacing and clearing exclusion lists, and
checked the output using \dRp+.
I also tried a number of edge cases, including non-existent sequences,
temporary sequences, unlogged sequences, duplicate entries in the
EXCEPT list, schema-qualified sequence names, and sequences with the
same name in different schemas. All of these behaved as expected.
In addition, I verified that pg_dump correctly preserves the EXCEPT
(SEQUENCE...)clause and that psql tab completion suggests sequence
names correctly for both CREATE PUBLICATION and ALTER PUBLICATION.
Overall, the patch applied cleanly, built successfully, and all the
test cases I tried worked as expected. I didn't notice any issues
during testing.
Regards,
solai
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
@ 2026-06-12 05:28 ` Peter Smith <[email protected]>
2026-06-15 10:14 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
1 sibling, 1 reply; 99+ messages in thread
From: Peter Smith @ 2026-06-12 05:28 UTC (permalink / raw)
To: Shlok Kyal <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
Some comments for v9-0002.
======
APPLY:
0.
The patch does not apply cleanly:
$ git apply ../patches_misc/v9-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLICA.patch
error: patch failed: doc/src/sgml/ref/alter_publication.sgml:277
======
doc/src/sgml/ref/alter_publication.sgml
1.
+ <para>
+ Reset the publication to be a <literal>ALL SEQUENCES</literal> publication
+ with no excluded sequences:
Personally, I don't see how it is good to have "publication" 2x and
"sequences" 2x in the same sentence.
SUGGESTION
Reset the publication to be <literal>ALL SEQUENCES</literal> with no exclusions:
~
I know you're only following the same pattern as "Reset the
publication to be a FOR ALL TABLES publication with no excluded
tables:". It is good to be consistent, but when the original text is
poor, copying it doesn't make it better.
Things like this fall into a grey-zone because, unless they get fixed
"in-passing", nothing ever changes:
a) I think a patch to only change the original wording would be
rejected because it is too trivial.
b) OTOH, when the original is used as a precedent, the poor wording
just spreads further.
~
Anyway, if your chose not to reword this, then there is a typo /a ALL
SEQUENCES publication/an ALL SEQUENCES publication/
======
src/backend/commands/publicationcmds.c
get_delete_rels:
2.
+ /* look up the cache for the old relmap */
/look up/Look up/
~~~
3.
+ if(found)
+ break;
Missing space after 'if'.
~~~
4.
+ oldrel = palloc0_object (PublicationRelInfo);
Unwanted space after function name.
======
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 99+ messages in thread
* Re: Support EXCEPT for ALL SEQUENCES publications
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
@ 2026-06-15 10:14 ` Shlok Kyal <[email protected]>
0 siblings, 0 replies; 99+ messages in thread
From: Shlok Kyal @ 2026-06-15 10:14 UTC (permalink / raw)
To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; vignesh C <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, 12 Jun 2026 at 10:59, Peter Smith <[email protected]> wrote:
>
> Some comments for v9-0002.
>
> ======
> APPLY:
>
> 0.
> The patch does not apply cleanly:
>
> $ git apply ../patches_misc/v9-0002-Support-EXCEPT-for-ALL-SEQUENCES-in-ALTER-PUBLICA.patch
> error: patch failed: doc/src/sgml/ref/alter_publication.sgml:277
>
> ======
> doc/src/sgml/ref/alter_publication.sgml
>
> 1.
> + <para>
> + Reset the publication to be a <literal>ALL SEQUENCES</literal> publication
> + with no excluded sequences:
>
> Personally, I don't see how it is good to have "publication" 2x and
> "sequences" 2x in the same sentence.
>
> SUGGESTION
> Reset the publication to be <literal>ALL SEQUENCES</literal> with no exclusions:
>
> ~
>
> I know you're only following the same pattern as "Reset the
> publication to be a FOR ALL TABLES publication with no excluded
> tables:". It is good to be consistent, but when the original text is
> poor, copying it doesn't make it better.
>
> Things like this fall into a grey-zone because, unless they get fixed
> "in-passing", nothing ever changes:
> a) I think a patch to only change the original wording would be
> rejected because it is too trivial.
> b) OTOH, when the original is used as a precedent, the poor wording
> just spreads further.
>
> ~
>
> Anyway, if your chose not to reword this, then there is a typo /a ALL
> SEQUENCES publication/an ALL SEQUENCES publication/
>
Yes, I wanted the text to be consistent with the existing doc. But
after reading your suggestion, I feel your version is more
appropriate. I have updated the patch to use it.
> ======
> src/backend/commands/publicationcmds.c
>
> get_delete_rels:
>
> 2.
> + /* look up the cache for the old relmap */
>
> /look up/Look up/
>
> ~~~
>
> 3.
> + if(found)
> + break;
>
> Missing space after 'if'.
>
> ~~~
>
> 4.
> + oldrel = palloc0_object (PublicationRelInfo);
>
> Unwanted space after function name.
>
I have addressed the remaining comments and have attached the updated
v10 patch in [1].
[1]: https://www.postgresql.org/message-id/CANhcyEVDStqoR3qDSgsv5VEuBMzMX4YpdEfW-7VPX3c5Vf1YJA%40mail.gma...
Thanks,
Shlok Kyal
^ permalink raw reply [nested|flat] 99+ messages in thread
end of thread, other threads:[~2026-07-08 12:08 UTC | newest]
Thread overview: 99+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2018-12-21 11:54 [PATCH v10 2/5] Allow CLUSTER and VACUUM FULL to change tablespace. Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v16 4/7] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v26 3/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v20 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v33 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v30 5/6] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v19 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v31 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v25 4/6] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v32 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v23 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v34 6/8] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v29 5/7] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v15 2/6] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v27 3/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH 3/4] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v17 5/8] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v21 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v22 4/5] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2020-03-24 15:16 [PATCH v18 5/6] Allow CLUSTER and VACUUM FULL to change tablespace Alexey Kondratov <[email protected]>
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-25 07:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-26 06:00 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 03:37 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-05-27 13:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-05-27 21:51 ` Re: Support EXCEPT for ALL SEQUENCES publications Zsolt Parragi <[email protected]>
2026-05-29 11:59 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-02 00:07 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-05 07:44 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-12 05:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:13 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-17 07:06 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-18 11:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-22 04:35 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-22 21:03 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-23 09:33 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-24 06:04 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-24 09:51 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 08:47 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-25 09:16 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-25 23:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-26 08:53 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-26 12:38 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-29 00:31 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-30 06:43 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 07:46 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-01 08:30 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-01 23:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-07-02 06:22 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-03 05:53 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-03 09:48 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-06 06:04 ` Re: Support EXCEPT for ALL SEQUENCES publications Ashutosh Sharma <[email protected]>
2026-07-07 06:36 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-07-08 02:25 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-07-08 12:08 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
2026-06-30 23:49 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-29 10:32 ` Re: Support EXCEPT for ALL SEQUENCES publications shveta malik <[email protected]>
2026-06-23 11:39 ` Re: Support EXCEPT for ALL SEQUENCES publications solai v <[email protected]>
2026-06-12 05:28 ` Re: Support EXCEPT for ALL SEQUENCES publications Peter Smith <[email protected]>
2026-06-15 10:14 ` Re: Support EXCEPT for ALL SEQUENCES publications Shlok Kyal <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox