mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
Allow TRUNCATE command to truncate foreign tables.
This commit introduces new foreign data wrapper API for TRUNCATE. It extends TRUNCATE command so that it accepts foreign tables as the targets to truncate and invokes that API. Also it extends postgres_fdw so that it can issue TRUNCATE command to foreign servers, by adding new routine for that TRUNCATE API. The information about options specified in TRUNCATE command, e.g., ONLY, CACADE, etc is passed to FDW via API. The list of foreign tables to truncate is also passed to FDW. FDW truncates the foreign data sources that the passed foreign tables specify, based on those information. For example, postgres_fdw constructs TRUNCATE command using them and issues it to the foreign server. For performance, TRUNCATE command invokes the FDW routine for TRUNCATE once per foreign server that foreign tables to truncate belong to. Author: Kazutaka Onishi, Kohei KaiGai, slightly modified by Fujii Masao Reviewed-by: Bharath Rupireddy, Michael Paquier, Zhihong Yu, Alvaro Herrera, Stephen Frost, Ashutosh Bapat, Amit Langote, Daniel Gustafsson, Ibrar Ahmed, Fujii Masao Discussion: https://postgr.es/m/CAOP8fzb_gkReLput7OvOK+8NHgw-RKqNv59vem7=524krQTcWA@mail.gmail.com Discussion: https://postgr.es/m/CAJuF6cMWDDqU-vn_knZgma+2GMaout68YUgn1uyDnexRhqqM5Q@mail.gmail.com
This commit is contained in:
@ -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"
|
||||
@ -310,6 +311,21 @@ struct DropRelationCallbackState
|
||||
#define ATT_FOREIGN_TABLE 0x0020
|
||||
#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.
|
||||
@ -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,14 +1902,63 @@ 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
|
||||
@ -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)));
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -704,6 +704,7 @@ ForeignScanState
|
||||
ForeignServer
|
||||
ForeignServerInfo
|
||||
ForeignTable
|
||||
ForeignTruncateInfo
|
||||
ForkNumber
|
||||
FormData_pg_aggregate
|
||||
FormData_pg_am
|
||||
|
Reference in New Issue
Block a user