aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/tablecmds.c151
-rw-r--r--src/backend/replication/logical/worker.c11
-rw-r--r--src/include/commands/tablecmds.h18
-rw-r--r--src/include/foreign/fdwapi.h8
-rw-r--r--src/include/utils/rel.h4
-rw-r--r--src/test/regress/expected/foreign_data.out8
-rw-r--r--src/tools/pgindent/typedefs.list1
7 files changed, 179 insertions, 22 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 87f9bdaef05..1f19629a949 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -59,6 +59,7 @@
#include "commands/typecmds.h"
#include "commands/user.h"
#include "executor/executor.h"
+#include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -311,6 +312,21 @@ struct DropRelationCallbackState
#define ATT_PARTITIONED_INDEX 0x0040
/*
+ * ForeignTruncateInfo
+ *
+ * Information related to truncation of foreign tables. This is used for
+ * the elements in a hash table. It uses the server OID as lookup key,
+ * and includes a per-server list of all foreign tables involved in the
+ * truncation.
+ */
+typedef struct ForeignTruncateInfo
+{
+ Oid serverid;
+ List *rels;
+ List *rels_extra;
+} ForeignTruncateInfo;
+
+/*
* Partition tables are expected to be dropped when the parent partitioned
* table gets dropped. Hence for partitioning we use AUTO dependency.
* Otherwise, for regular inheritance use NORMAL dependency.
@@ -1589,7 +1605,10 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
*
* This is a multi-relation truncate. We first open and grab exclusive
* lock on all relations involved, checking permissions and otherwise
- * verifying that the relation is OK for truncation. In CASCADE mode,
+ * verifying that the relation is OK for truncation. Note that if relations
+ * are foreign tables, at this stage, we have not yet checked that their
+ * foreign data in external data sources are OK for truncation. These are
+ * checked when foreign data are actually truncated later. In CASCADE mode,
* relations having FK references to the targeted relations are automatically
* added to the group; in RESTRICT mode, we check that all FK references are
* internal to the group that's being truncated. Finally all the relations
@@ -1600,6 +1619,7 @@ ExecuteTruncate(TruncateStmt *stmt)
{
List *rels = NIL;
List *relids = NIL;
+ List *relids_extra = NIL;
List *relids_logged = NIL;
ListCell *cell;
@@ -1636,6 +1656,9 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
+ relids_extra = lappend_int(relids_extra, (recurse ?
+ TRUNCATE_REL_CONTEXT_NORMAL :
+ TRUNCATE_REL_CONTEXT_ONLY));
/* Log this relation only if needed for logical decoding */
if (RelationIsLogicallyLogged(rel))
relids_logged = lappend_oid(relids_logged, myrelid);
@@ -1683,6 +1706,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
+ relids_extra = lappend_int(relids_extra,
+ TRUNCATE_REL_CONTEXT_CASCADING);
/* Log this relation only if needed for logical decoding */
if (RelationIsLogicallyLogged(rel))
relids_logged = lappend_oid(relids_logged, childrelid);
@@ -1695,7 +1720,7 @@ ExecuteTruncate(TruncateStmt *stmt)
errhint("Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.")));
}
- ExecuteTruncateGuts(rels, relids, relids_logged,
+ ExecuteTruncateGuts(rels, relids, relids_extra, relids_logged,
stmt->behavior, stmt->restart_seqs);
/* And close the rels */
@@ -1716,21 +1741,28 @@ ExecuteTruncate(TruncateStmt *stmt)
*
* explicit_rels is the list of Relations to truncate that the command
* specified. relids is the list of Oids corresponding to explicit_rels.
- * relids_logged is the list of Oids (a subset of relids) that require
- * WAL-logging. This is all a bit redundant, but the existing callers have
- * this information handy in this form.
+ * relids_extra is the list of integer values that deliver extra information
+ * about relations in explicit_rels. relids_logged is the list of Oids
+ * (a subset of relids) that require WAL-logging. This is all a bit redundant,
+ * but the existing callers have this information handy in this form.
*/
void
-ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
+ExecuteTruncateGuts(List *explicit_rels,
+ List *relids,
+ List *relids_extra,
+ List *relids_logged,
DropBehavior behavior, bool restart_seqs)
{
List *rels;
List *seq_relids = NIL;
+ HTAB *ft_htab = NULL;
EState *estate;
ResultRelInfo *resultRelInfos;
ResultRelInfo *resultRelInfo;
SubTransactionId mySubid;
ListCell *cell;
+ ListCell *lc1,
+ *lc2;
Oid *logrelids;
/*
@@ -1768,6 +1800,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
truncate_check_activity(rel);
rels = lappend(rels, rel);
relids = lappend_oid(relids, relid);
+ relids_extra = lappend_int(relids_extra,
+ TRUNCATE_REL_CONTEXT_CASCADING);
/* Log this relation only if needed for logical decoding */
if (RelationIsLogicallyLogged(rel))
relids_logged = lappend_oid(relids_logged, relid);
@@ -1868,15 +1902,64 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
*/
mySubid = GetCurrentSubTransactionId();
- foreach(cell, rels)
+ Assert(list_length(rels) == list_length(relids_extra));
+ forboth(lc1, rels, lc2, relids_extra)
{
- Relation rel = (Relation) lfirst(cell);
+ Relation rel = (Relation) lfirst(lc1);
+ int extra = lfirst_int(lc2);
/* Skip partitioned tables as there is nothing to do */
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
continue;
/*
+ * Build the lists of foreign tables belonging to each foreign server
+ * and pass each list to the foreign data wrapper's callback function,
+ * so that each server can truncate its all foreign tables in bulk.
+ * Each list is saved as a single entry in a hash table that uses the
+ * server OID as lookup key.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ Oid serverid = GetForeignServerIdByRelId(RelationGetRelid(rel));
+ bool found;
+ ForeignTruncateInfo *ft_info;
+
+ /* First time through, initialize hashtable for foreign tables */
+ if (!ft_htab)
+ {
+ HASHCTL hctl;
+
+ memset(&hctl, 0, sizeof(HASHCTL));
+ hctl.keysize = sizeof(Oid);
+ hctl.entrysize = sizeof(ForeignTruncateInfo);
+ hctl.hcxt = CurrentMemoryContext;
+
+ ft_htab = hash_create("TRUNCATE for Foreign Tables",
+ 32, /* start small and extend */
+ &hctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ }
+
+ /* Find or create cached entry for the foreign table */
+ ft_info = hash_search(ft_htab, &serverid, HASH_ENTER, &found);
+ if (!found)
+ {
+ ft_info->serverid = serverid;
+ ft_info->rels = NIL;
+ ft_info->rels_extra = NIL;
+ }
+
+ /*
+ * Save the foreign table in the entry of the server that the
+ * foreign table belongs to.
+ */
+ ft_info->rels = lappend(ft_info->rels, rel);
+ ft_info->rels_extra = lappend_int(ft_info->rels_extra, extra);
+ continue;
+ }
+
+ /*
* Normally, we need a transaction-safe truncation here. However, if
* the table was either created in the current (sub)transaction or has
* a new relfilenode in the current (sub)transaction, then we can just
@@ -1938,6 +2021,36 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
pgstat_count_truncate(rel);
}
+ /* Now go through the hash table, and truncate foreign tables */
+ if (ft_htab)
+ {
+ ForeignTruncateInfo *ft_info;
+ HASH_SEQ_STATUS seq;
+
+ hash_seq_init(&seq, ft_htab);
+
+ PG_TRY();
+ {
+ while ((ft_info = hash_seq_search(&seq)) != NULL)
+ {
+ FdwRoutine *routine = GetFdwRoutineByServerId(ft_info->serverid);
+
+ /* truncate_check_rel() has checked that already */
+ Assert(routine->ExecForeignTruncate != NULL);
+
+ routine->ExecForeignTruncate(ft_info->rels,
+ ft_info->rels_extra,
+ behavior,
+ restart_seqs);
+ }
+ }
+ PG_FINALLY();
+ {
+ hash_destroy(ft_htab);
+ }
+ PG_END_TRY();
+ }
+
/*
* Restart owned sequences if we were asked to.
*/
@@ -2023,12 +2136,24 @@ truncate_check_rel(Oid relid, Form_pg_class reltuple)
char *relname = NameStr(reltuple->relname);
/*
- * Only allow truncate on regular tables and partitioned tables (although,
- * the latter are only being included here for the following checks; no
- * physical truncation will occur in their case.)
+ * Only allow truncate on regular tables, foreign tables using foreign
+ * data wrappers supporting TRUNCATE and partitioned tables (although, the
+ * latter are only being included here for the following checks; no
+ * physical truncation will occur in their case.).
*/
- if (reltuple->relkind != RELKIND_RELATION &&
- reltuple->relkind != RELKIND_PARTITIONED_TABLE)
+ if (reltuple->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ Oid serverid = GetForeignServerIdByRelId(relid);
+ FdwRoutine *fdwroutine = GetFdwRoutineByServerId(serverid);
+
+ if (!fdwroutine->ExecForeignTruncate)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot truncate foreign table \"%s\"",
+ relname)));
+ }
+ else if (reltuple->relkind != RELKIND_RELATION &&
+ reltuple->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", relname)));
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 8da602d1636..fb3ba5c4159 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1795,6 +1795,7 @@ apply_handle_truncate(StringInfo s)
List *rels = NIL;
List *part_rels = NIL;
List *relids = NIL;
+ List *relids_extra = NIL;
List *relids_logged = NIL;
ListCell *lc;
@@ -1824,6 +1825,7 @@ apply_handle_truncate(StringInfo s)
remote_rels = lappend(remote_rels, rel);
rels = lappend(rels, rel->localrel);
relids = lappend_oid(relids, rel->localreloid);
+ relids_extra = lappend_int(relids_extra, TRUNCATE_REL_CONTEXT_NORMAL);
if (RelationIsLogicallyLogged(rel->localrel))
relids_logged = lappend_oid(relids_logged, rel->localreloid);
@@ -1862,6 +1864,7 @@ apply_handle_truncate(StringInfo s)
rels = lappend(rels, childrel);
part_rels = lappend(part_rels, childrel);
relids = lappend_oid(relids, childrelid);
+ relids_extra = lappend_int(relids_extra, TRUNCATE_REL_CONTEXT_CASCADING);
/* Log this relation only if needed for logical decoding */
if (RelationIsLogicallyLogged(childrel))
relids_logged = lappend_oid(relids_logged, childrelid);
@@ -1874,8 +1877,12 @@ apply_handle_truncate(StringInfo s)
* to replaying changes without further cascading. This might be later
* changeable with a user specified option.
*/
- ExecuteTruncateGuts(rels, relids, relids_logged, DROP_RESTRICT, restart_seqs);
-
+ ExecuteTruncateGuts(rels,
+ relids,
+ relids_extra,
+ relids_logged,
+ DROP_RESTRICT,
+ restart_seqs);
foreach(lc, remote_rels)
{
LogicalRepRelMapEntry *rel = lfirst(lc);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index b3d30acc359..b808a07e461 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -21,6 +21,16 @@
#include "storage/lock.h"
#include "utils/relcache.h"
+/*
+ * These values indicate how a relation was specified as the target to
+ * truncate in TRUNCATE command.
+ */
+#define TRUNCATE_REL_CONTEXT_NORMAL 1 /* specified without ONLY clause */
+#define TRUNCATE_REL_CONTEXT_ONLY 2 /* specified with ONLY clause */
+#define TRUNCATE_REL_CONTEXT_CASCADING 3 /* not specified but truncated
+ * due to dependency (e.g.,
+ * partition table) */
+
struct AlterTableUtilityContext; /* avoid including tcop/utility.h here */
@@ -56,8 +66,12 @@ extern void AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
extern void CheckTableNotInUse(Relation rel, const char *stmt);
extern void ExecuteTruncate(TruncateStmt *stmt);
-extern void ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
- DropBehavior behavior, bool restart_seqs);
+extern void ExecuteTruncateGuts(List *explicit_rels,
+ List *relids,
+ List *relids_extra,
+ List *relids_logged,
+ DropBehavior behavior,
+ bool restart_seqs);
extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass);
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 10d29ff292a..4ebbca6de92 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -160,6 +160,11 @@ typedef bool (*AnalyzeForeignTable_function) (Relation relation,
typedef List *(*ImportForeignSchema_function) (ImportForeignSchemaStmt *stmt,
Oid serverOid);
+typedef void (*ExecForeignTruncate_function) (List *rels,
+ List *rels_extra,
+ DropBehavior behavior,
+ bool restart_seqs);
+
typedef Size (*EstimateDSMForeignScan_function) (ForeignScanState *node,
ParallelContext *pcxt);
typedef void (*InitializeDSMForeignScan_function) (ForeignScanState *node,
@@ -255,6 +260,9 @@ typedef struct FdwRoutine
/* Support functions for IMPORT FOREIGN SCHEMA */
ImportForeignSchema_function ImportForeignSchema;
+ /* Support functions for TRUNCATE */
+ ExecForeignTruncate_function ExecForeignTruncate;
+
/* Support functions for parallelism under Gather node */
IsForeignScanParallelSafe_function IsForeignScanParallelSafe;
EstimateDSMForeignScan_function EstimateDSMForeignScan;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 9a3a03e5207..34e25eb5978 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -634,7 +634,8 @@ typedef struct ViewOptions
* WAL stream.
*
* We don't log information for unlogged tables (since they don't WAL log
- * anyway) and for system tables (their content is hard to make sense of, and
+ * anyway), for foreign tables (since they don't WAL log, either),
+ * and for system tables (their content is hard to make sense of, and
* it would complicate decoding slightly for little gain). Note that we *do*
* log information for user defined catalog tables since they presumably are
* interesting to the user...
@@ -642,6 +643,7 @@ typedef struct ViewOptions
#define RelationIsLogicallyLogged(relation) \
(XLogLogicalInfoActive() && \
RelationNeedsWAL(relation) && \
+ (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE && \
!IsCatalogRelation(relation))
/* routines in utils/cache/relcache.c */
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index e4cdb780d0a..5385f98a0fe 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -1807,9 +1807,9 @@ Inherits: fd_pt1
-- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE ft2; -- ERROR
-ERROR: "ft2" is not a table
+ERROR: foreign-data wrapper "dummy" has no handler
TRUNCATE fd_pt1; -- ERROR
-ERROR: "ft2" is not a table
+ERROR: foreign-data wrapper "dummy" has no handler
DROP TABLE fd_pt1 CASCADE;
NOTICE: drop cascades to foreign table ft2
-- IMPORT FOREIGN SCHEMA
@@ -2032,9 +2032,9 @@ ALTER FOREIGN TABLE fd_pt2_1 ADD CONSTRAINT fd_pt2chk1 CHECK (c1 > 0);
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
-- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE fd_pt2_1; -- ERROR
-ERROR: "fd_pt2_1" is not a table
+ERROR: foreign-data wrapper "dummy" has no handler
TRUNCATE fd_pt2; -- ERROR
-ERROR: "fd_pt2_1" is not a table
+ERROR: foreign-data wrapper "dummy" has no handler
DROP FOREIGN TABLE fd_pt2_1;
DROP TABLE fd_pt2;
-- foreign table cannot be part of partition tree made of temporary
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 200015cac79..c7aff677d4b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -704,6 +704,7 @@ ForeignScanState
ForeignServer
ForeignServerInfo
ForeignTable
+ForeignTruncateInfo
ForkNumber
FormData_pg_aggregate
FormData_pg_am