On Mon, Jan 20, 2020 at 11:30:34AM +0900, Kohei KaiGai wrote: > Sorry, it was a midnight job on Friday.
Should I be, err, worried about that? ;) > Please check the attached patch. + switch (behavior) + { + case DROP_RESTRICT: + appendStringInfoString(buf, " RESTRICT"); + break; + case DROP_CASCADE: + appendStringInfoString(buf, " CASCADE"); + break; + default: + elog(ERROR, "Bug? unexpected DropBehavior (%d)", (int)behavior); + break; + } Here, you can actually remove the default clause. By doing so, compilation would generate a warning if a new value is added to DropBehavior if it is not listed. So anybody adding a new value to the enum will need to think about this code path. + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("foreign data wrapper \"%s\" on behalf of the foreign table \"%s\" does not support TRUNCATE", + fdw->fdwname, relname))); I see two problems here: - The error message is too complicated. I would just use "cannot truncate foreign table \"%s\"". - The error code should be ERRCODE_FEATURE_NOT_SUPPORTED. The docs for the FDW description can be improved. I found that a large portion of it used rather unclear English, and that things were not clear enough regarding the use of a list of relations, when an error is raised because ExecForeignTruncate is NULL, etc. I have also cut the last paragraph which is actually implementation-specific (think for example about callbacks at xact commit/abort time). Documentation needs to be added to postgres_fdw about the truncation support. Particularly, providing details about the possibility to do truncates in our shot for a set of relations so as dependencies are automatically handled is an advantage to mention. There is no need to include the truncate routine in ForeignTruncateInfo, as the server OID can be used to find it. Another thing is that I would prefer splitting the patch into two separate commits, so attached are two patches: - 0001 for the addition of the in-core API - 0002 for the addition of support in postgres_fdw. I have spent a good amount of time polishing 0001, tweaking the docs, comments, error messages and a bit its logic. I am getting comfortable with it, but it still needs an extra lookup, an indent run which has some noise and I lacked of time today. 0002 has some of its issues fixed and I have not reviewed it fully yet. There are still some places not adapted in it (why do you use "Bug?" in all your elog() messages by the way?), so the postgres_fdw part needs more attention. Could you think about some docs for it by the way? -- Michael
From ed63df0fdeac43443b8e15709c19fe6c1f38a1f8 Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@paquier.xyz> Date: Mon, 20 Jan 2020 22:39:34 +0900 Subject: [PATCH 1/2] Add FDW callback for support of TRUNCATE --- src/include/foreign/fdwapi.h | 7 ++ src/backend/commands/tablecmds.c | 110 ++++++++++++++++++++- src/test/regress/expected/foreign_data.out | 8 +- doc/src/sgml/fdwhandler.sgml | 36 +++++++ 4 files changed, 153 insertions(+), 8 deletions(-) diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h index 95556dfb15..0a9f36735e 100644 --- a/src/include/foreign/fdwapi.h +++ b/src/include/foreign/fdwapi.h @@ -151,6 +151,10 @@ typedef bool (*AnalyzeForeignTable_function) (Relation relation, typedef List *(*ImportForeignSchema_function) (ImportForeignSchemaStmt *stmt, Oid serverOid); +typedef void (*ExecForeignTruncate_function) (List *frels_list, + DropBehavior behavior, + bool restart_seqs); + typedef Size (*EstimateDSMForeignScan_function) (ForeignScanState *node, ParallelContext *pcxt); typedef void (*InitializeDSMForeignScan_function) (ForeignScanState *node, @@ -236,6 +240,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/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 30b72b6297..f83f88a82f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -60,6 +60,7 @@ #include "commands/user.h" #include "executor/executor.h" #include "foreign/foreign.h" +#include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -295,6 +296,20 @@ struct DropRelationCallbackState #define ATT_FOREIGN_TABLE 0x0020 #define ATT_PARTITIONED_INDEX 0x0040 +/* + * ForeignTruncateInfo + * + * Information related to truncation of foreign tables. This + * is a single entry part of a hash table using the server OID + * as key, with a list of all foreign tables involved. + */ +typedef struct +{ + Oid server_oid; + List *frels_list; +} ForeignTruncateInfo; + + /* * Partition tables are expected to be dropped when the parent partitioned * table gets dropped. Hence for partitioning we use AUTO dependency. @@ -1647,6 +1662,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, { List *rels; List *seq_relids = NIL; + HTAB *ft_htab = NULL; EState *estate; ResultRelInfo *resultRelInfos; ResultRelInfo *resultRelInfo; @@ -1792,6 +1808,57 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) continue; + /* + * If truncating a foreign table, the foreign data wrapper + * callback for TRUNCATE is called once for each server + * with a list of all the relations to process linked to this + * server. The list of relations for each server is saved as + * a single entry of a hash table that uses the server OID as + * lookup key. Once the full set of lists is built, all the + * entries of the hash table are scanned, and the list of relations + * associated to the server is passed down to the TRUNCATE callback + * of its foreign data wrapper. + */ + if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + { + Oid frel_oid = RelationGetRelid(rel); + Oid server_oid = GetForeignServerIdByRelId(frel_oid); + bool found; + ForeignTruncateInfo *ft_info; + + /* if the hash table does not exist yet, initialize it */ + 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); + } + + /* + * Look after the entry of the server in the hash table, + * and initialize it if the entry does not exist yet. + */ + ft_info = hash_search(ft_htab, &server_oid, HASH_ENTER, &found); + if (!found) + { + ft_info->server_oid = server_oid; + ft_info->frels_list = NIL; + + } + + /* save the relation in the list */ + ft_info->frels_list = lappend(ft_info->frels_list, rel); + continue; + } + /* * Normally, we need a transaction-safe truncation here. However, if * the table was either created in the current (sub)transaction or has @@ -1852,6 +1919,29 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, pgstat_count_truncate(rel); } + /* + * Now go through the hash table, and process each entry associated + * to the servers involved in the TRUNCATE. + */ + if (ft_htab) + { + ForeignTruncateInfo *ft_info; + HASH_SEQ_STATUS seq; + + hash_seq_init(&seq, ft_htab); + + while ((ft_info = hash_seq_search(&seq)) != NULL) + { + FdwRoutine *routine = GetFdwRoutineByServerId(ft_info->server_oid); + + /* truncate_check_rel() has checked that already */ + Assert(routine->ExecForeignTruncate != NULL); + routine->ExecForeignTruncate(ft_info->frels_list, + behavior, + restart_seqs); + } + } + /* * Restart owned sequences if we were asked to. */ @@ -1939,12 +2029,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, + * 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.) + * physical truncation will occur in their case.). */ - if (reltuple->relkind != RELKIND_RELATION && - reltuple->relkind != RELKIND_PARTITIONED_TABLE) + if (reltuple->relkind == RELKIND_FOREIGN_TABLE) + { + Oid server_id = GetForeignServerIdByRelId(relid); + FdwRoutine *fdwroutine = GetFdwRoutineByServerId(server_id); + + 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/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out index b9e25820bc..e2c0bcea51 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/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml index 6587678af2..f2416c9074 100644 --- a/doc/src/sgml/fdwhandler.sgml +++ b/doc/src/sgml/fdwhandler.sgml @@ -968,6 +968,42 @@ EndDirectModify(ForeignScanState *node); </sect2> + <sect2 id="fdw-callbacks-truncate"> + <title>FDW Routines for Truncate</title> +<programlisting> +void +ExecForeignTruncate(List *frels_list, + DropBehavior behavior, bool restart_seqs); +</programlisting> + <para> + Truncate a set of foreign tables defined by + <literal>frels_list</literal> belonging to the same foreign server. + This optional function is called during execution of + <command>TRUNCATE</command> for each foreign server being involved + in one <command>TRUNCATE</command> command (note that invocations + are not per foreign table). + </para> + + <para> + If the <function>ExecForeignTruncate</function> pointer is set to + <literal>NULL</literal>, attempts to truncate the foreign table will + fail with an error message. + </para> + + <para> + <literal>behavior</literal> defines how foreign tables should + be truncated, using as possible values <literal>DROP_RESTRICT</literal> + and <literal>DROP_CASCADE</literal> (to map with the equivalents of + <command>TRUNCATE</command>). + </para> + + <para> + <literal>restart_seqs</literal> is set to <literal>true</literal> + if <literal>RESTART IDENTITY</literal> was supplied in the + <command>TRUNCATE</command>. + </para> + </sect2> + <sect2 id="fdw-callbacks-row-locking"> <title>FDW Routines for Row Locking</title> -- 2.25.0
From 3918ddb188d62accaac7e1dbe5d8459f4fe72b54 Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@paquier.xyz> Date: Mon, 20 Jan 2020 22:40:06 +0900 Subject: [PATCH 2/2] Add support for TRUNCATE in postgres_fdw --- contrib/postgres_fdw/deparse.c | 31 ++++ .../postgres_fdw/expected/postgres_fdw.out | 144 ++++++++++++++++++ contrib/postgres_fdw/postgres_fdw.c | 51 +++++++ contrib/postgres_fdw/postgres_fdw.h | 2 + contrib/postgres_fdw/sql/postgres_fdw.sql | 51 +++++++ 5 files changed, 279 insertions(+) diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index a3639e9604..94e8f71d0c 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -2098,6 +2098,37 @@ deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs) deparseRelation(buf, rel); } +/* + * Construct a simple "TRUNCATE rel" statement + */ +void +deparseTruncateSql(StringInfo buf, List *frels_list, + DropBehavior behavior, bool restart_seqs) +{ + ListCell *lc; + + appendStringInfoString(buf, "TRUNCATE "); + foreach (lc, frels_list) + { + Relation frel = lfirst(lc); + + if (lc != list_head(frels_list)) + appendStringInfoString(buf, ", "); + deparseRelation(buf, frel); + } + appendStringInfo(buf, " %s IDENTITY", + restart_seqs ? "RESTART" : "CONTINUE"); + switch (behavior) + { + case DROP_RESTRICT: + appendStringInfoString(buf, " RESTRICT"); + break; + case DROP_CASCADE: + appendStringInfoString(buf, " CASCADE"); + break; + } +} + /* * Construct name to use for given column, and emit it into buf. * If it has a column_name FDW option, use that instead of attribute name. diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 84fd3ad2e0..ef10c8f709 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -8168,6 +8168,150 @@ select * from rem3; drop foreign table rem3; drop table loc3; +-- =================================================================== +-- test for TRUNCATE +-- =================================================================== +CREATE TABLE tru_nt (id int, x text); +CREATE FOREIGN TABLE tru_ft (id int, x text) + SERVER loopback OPTIONS (table_name 'tru_nt'); +INSERT INTO tru_nt (SELECT x,md5(x::text) FROM generate_series(1,10) x); +CREATE TABLE tru_pt (id int, y text) PARTITION BY HASH(id); +CREATE TABLE tru_pt__p0 PARTITION OF tru_pt + FOR VALUES WITH (MODULUS 2, REMAINDER 0); +CREATE TABLE __tru_pt__p1 (id int, y text); +CREATE FOREIGN TABLE tru_pt__p1 PARTITION OF tru_pt + FOR VALUES WITH (MODULUS 2, REMAINDER 1) + SERVER loopback OPTIONS (table_name '__tru_pt__p1'); +INSERT INTO tru_pt (SELECT x,md5(x::text) FROM generate_series(11,30) x); +CREATE TABLE tru_pk (id int primary key, x text); +INSERT INTO tru_pk (SELECT x,md5((x+1)::text) FROM generate_series(1,10) x); +CREATE TABLE tru_fk (fkey int references tru_pk(id)); +INSERT INTO tru_fk (SELECT x % 10 + 1 FROM generate_series(1,30) x); +CREATE FOREIGN TABLE tru_ft_ref (id int, x text) + SERVER loopback OPTIONS (table_name 'tru_pk'); +-- normal truncate +SELECT * FROM tru_ft; + id | x +----+---------------------------------- + 1 | c4ca4238a0b923820dcc509a6f75849b + 2 | c81e728d9d4c2f636f067f89cc14862c + 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 4 | a87ff679a2f3e71d9181a67b7542122c + 5 | e4da3b7fbbce2345d7772b0674a318d5 + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 7 | 8f14e45fceea167a5a36dedd4bea2543 + 8 | c9f0f895fb98ab9159f51fd0297e236d + 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 10 | d3d9446802a44259755d38e6d163e820 +(10 rows) + +TRUNCATE tru_ft; +SELECT * FROM tru_nt; + id | x +----+--- +(0 rows) + +SELECT * FROM tru_ft; + id | x +----+--- +(0 rows) + +-- partition table mixtured by table and foreign table +SELECT * FROM tru_pt; + id | y +----+---------------------------------- + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 13 | c51ce410c124a10e0db5e4b97fc2af39 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 17 | 70efdf2ec9b086079795c442636b55fb + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 23 | 37693cfc748049e45d87b8c7d8b9aacd + 25 | 8e296a067a37563370ded05f5a3bf3ec + 26 | 4e732ced3463d06de0ca9a15b6153677 + 27 | 02e74f10e0327ad868d138f2b4fdd6f0 + 28 | 33e75ff09dd601bbe69f351039152189 + 30 | 34173cb38f07f89ddbebc2ac9128303f + 11 | 6512bd43d9caa6e02c990b0a82652dca + 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + 19 | 1f0e3dad99908345f7439f8ffabdffc4 + 20 | 98f13708210194c475687be6106a3b84 + 21 | 3c59dc048e8850243be8079a5c74d079 + 22 | b6d767d2f8ed5d21a44b0e5886680cb9 + 24 | 1ff1de774005f8da13f42943881c655f + 29 | 6ea9ab1baa0efb9e19094440c317e21b +(20 rows) + +TRUNCATE tru_pt; +SELECT * FROM tru_pt; + id | y +----+--- +(0 rows) + +SELECT * FROM tru_pt__p0; + id | y +----+--- +(0 rows) + +SELECT * FROM tru_pt__p1; + id | y +----+--- +(0 rows) + +SELECT * FROM __tru_pt__p1; + id | y +----+--- +(0 rows) + +-- 'CASCADE' option +SELECT * FROM tru_ft_ref; + id | x +----+---------------------------------- + 1 | c81e728d9d4c2f636f067f89cc14862c + 2 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 3 | a87ff679a2f3e71d9181a67b7542122c + 4 | e4da3b7fbbce2345d7772b0674a318d5 + 5 | 1679091c5a880faf6fb5e6087eb1b2dc + 6 | 8f14e45fceea167a5a36dedd4bea2543 + 7 | c9f0f895fb98ab9159f51fd0297e236d + 8 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 9 | d3d9446802a44259755d38e6d163e820 + 10 | 6512bd43d9caa6e02c990b0a82652dca +(10 rows) + +TRUNCATE tru_ft_ref; -- failed +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "tru_fk" references "tru_pk". +HINT: Truncate table "tru_fk" at the same time, or use TRUNCATE ... CASCADE. +CONTEXT: remote SQL command: TRUNCATE public.tru_pk CONTINUE IDENTITY RESTRICT +TRUNCATE tru_ft_ref CASCADE; +SELECT * FROM tru_ft_ref; + id | x +----+--- +(0 rows) + +-- truncate two tables at a command +INSERT INTO tru_ft (SELECT x,md5((x+2)::text) FROM generate_series(1,8) x); +INSERT INTO tru_ft_ref (SELECT x,md5((x+3)::text) FROM generate_series(1,8) x); +SELECT * FROM tru_ft a, tru_ft_ref b WHERE a.id = b.id; + id | x | id | x +----+----------------------------------+----+---------------------------------- + 1 | eccbc87e4b5ce2fe28308fd9f2a7baf3 | 1 | a87ff679a2f3e71d9181a67b7542122c + 2 | a87ff679a2f3e71d9181a67b7542122c | 2 | e4da3b7fbbce2345d7772b0674a318d5 + 3 | e4da3b7fbbce2345d7772b0674a318d5 | 3 | 1679091c5a880faf6fb5e6087eb1b2dc + 4 | 1679091c5a880faf6fb5e6087eb1b2dc | 4 | 8f14e45fceea167a5a36dedd4bea2543 + 5 | 8f14e45fceea167a5a36dedd4bea2543 | 5 | c9f0f895fb98ab9159f51fd0297e236d + 6 | c9f0f895fb98ab9159f51fd0297e236d | 6 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 7 | 45c48cce2e2d7fbdea1afc51c7c6ad26 | 7 | d3d9446802a44259755d38e6d163e820 + 8 | d3d9446802a44259755d38e6d163e820 | 8 | 6512bd43d9caa6e02c990b0a82652dca +(8 rows) + +TRUNCATE tru_ft, tru_ft_ref CASCADE; +SELECT * FROM tru_ft a, tru_ft_ref b WHERE a.id = b.id; + id | x | id | x +----+---+----+--- +(0 rows) + -- =================================================================== -- test IMPORT FOREIGN SCHEMA -- =================================================================== diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 2175dff824..ded54ab5e9 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -373,6 +373,9 @@ static void postgresExplainForeignModify(ModifyTableState *mtstate, ExplainState *es); static void postgresExplainDirectModify(ForeignScanState *node, ExplainState *es); +static void postgresExecForeignTruncate(List *frels_list, + DropBehavior behavior, + bool restart_seqs); static bool postgresAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages); @@ -546,6 +549,9 @@ postgres_fdw_handler(PG_FUNCTION_ARGS) routine->ExplainForeignModify = postgresExplainForeignModify; routine->ExplainDirectModify = postgresExplainDirectModify; + /* Support function for TRUNCATE */ + routine->ExecForeignTruncate = postgresExecForeignTruncate; + /* Support functions for ANALYZE */ routine->AnalyzeForeignTable = postgresAnalyzeForeignTable; @@ -2656,6 +2662,51 @@ postgresExplainDirectModify(ForeignScanState *node, ExplainState *es) } } +/* + * postgresExecForeignTruncate + * It propagates TRUNCATE command to the remote host inside of the + * transaction block. + */ +static void +postgresExecForeignTruncate(List *frels_list, + DropBehavior behavior, + bool restart_seqs) +{ + Oid server_id = InvalidOid; + UserMapping *user = NULL; + PGconn *conn = NULL; + PGresult *res; + StringInfoData sql; + ListCell *lc; + + /* pick up remote connection, and sanity checks */ + foreach (lc, frels_list) + { + Relation frel = lfirst(lc); + Oid frel_oid = RelationGetRelid(frel); + + if (!OidIsValid(server_id)) + { + server_id = GetForeignServerIdByRelId(frel_oid); + user = GetUserMapping(GetUserId(), server_id); + conn = GetConnection(user, false); + } + else if (server_id != GetForeignServerIdByRelId(frel_oid)) + elog(ERROR, "Bug? inconsistent Server-IDs were supplied"); + } + /* set up remote query */ + initStringInfo(&sql); + deparseTruncateSql(&sql, frels_list, behavior, restart_seqs); + /* run remote query */ + if (!PQsendQuery(conn, sql.data)) + pgfdw_report_error(ERROR, NULL, conn, false, sql.data); + res = pgfdw_get_result(conn, sql.data); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pgfdw_report_error(ERROR, res, conn, true, sql.data); + /* clean-up */ + PQclear(res); + pfree(sql.data); +} /* * estimate_path_cost_size diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index eef410db39..1d75bf363a 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -190,6 +190,8 @@ extern void deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel); extern void deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs); +extern void deparseTruncateSql(StringInfo buf, List *frels_list, + DropBehavior behavior, bool restart_seqs); extern void deparseStringLiteral(StringInfo buf, const char *val); extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel); extern Expr *find_em_expr_for_input_target(PlannerInfo *root, diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index acd7275c72..95c6e45975 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -2287,6 +2287,57 @@ select * from rem3; drop foreign table rem3; drop table loc3; +-- =================================================================== +-- test for TRUNCATE +-- =================================================================== +CREATE TABLE tru_nt (id int, x text); +CREATE FOREIGN TABLE tru_ft (id int, x text) + SERVER loopback OPTIONS (table_name 'tru_nt'); +INSERT INTO tru_nt (SELECT x,md5(x::text) FROM generate_series(1,10) x); + +CREATE TABLE tru_pt (id int, y text) PARTITION BY HASH(id); +CREATE TABLE tru_pt__p0 PARTITION OF tru_pt + FOR VALUES WITH (MODULUS 2, REMAINDER 0); +CREATE TABLE __tru_pt__p1 (id int, y text); +CREATE FOREIGN TABLE tru_pt__p1 PARTITION OF tru_pt + FOR VALUES WITH (MODULUS 2, REMAINDER 1) + SERVER loopback OPTIONS (table_name '__tru_pt__p1'); +INSERT INTO tru_pt (SELECT x,md5(x::text) FROM generate_series(11,30) x); + +CREATE TABLE tru_pk (id int primary key, x text); +INSERT INTO tru_pk (SELECT x,md5((x+1)::text) FROM generate_series(1,10) x); +CREATE TABLE tru_fk (fkey int references tru_pk(id)); +INSERT INTO tru_fk (SELECT x % 10 + 1 FROM generate_series(1,30) x); +CREATE FOREIGN TABLE tru_ft_ref (id int, x text) + SERVER loopback OPTIONS (table_name 'tru_pk'); + +-- normal truncate +SELECT * FROM tru_ft; +TRUNCATE tru_ft; +SELECT * FROM tru_nt; +SELECT * FROM tru_ft; + +-- partition table mixtured by table and foreign table +SELECT * FROM tru_pt; +TRUNCATE tru_pt; +SELECT * FROM tru_pt; +SELECT * FROM tru_pt__p0; +SELECT * FROM tru_pt__p1; +SELECT * FROM __tru_pt__p1; + +-- 'CASCADE' option +SELECT * FROM tru_ft_ref; +TRUNCATE tru_ft_ref; -- failed +TRUNCATE tru_ft_ref CASCADE; +SELECT * FROM tru_ft_ref; + +-- truncate two tables at a command +INSERT INTO tru_ft (SELECT x,md5((x+2)::text) FROM generate_series(1,8) x); +INSERT INTO tru_ft_ref (SELECT x,md5((x+3)::text) FROM generate_series(1,8) x); +SELECT * FROM tru_ft a, tru_ft_ref b WHERE a.id = b.id; +TRUNCATE tru_ft, tru_ft_ref CASCADE; +SELECT * FROM tru_ft a, tru_ft_ref b WHERE a.id = b.id; + -- =================================================================== -- test IMPORT FOREIGN SCHEMA -- =================================================================== -- 2.25.0
signature.asc
Description: PGP signature