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:
Fujii Masao 2021-04-08 20:56:08 +09:00
parent 50e17ad281
commit 8ff1c94649
17 changed files with 725 additions and 31 deletions

View File

@ -92,7 +92,6 @@ static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
static void disconnect_pg_server(ConnCacheEntry *entry); static void disconnect_pg_server(ConnCacheEntry *entry);
static void check_conn_params(const char **keywords, const char **values, UserMapping *user); static void check_conn_params(const char **keywords, const char **values, UserMapping *user);
static void configure_remote_session(PGconn *conn); static void configure_remote_session(PGconn *conn);
static void do_sql_command(PGconn *conn, const char *sql);
static void begin_remote_xact(ConnCacheEntry *entry); static void begin_remote_xact(ConnCacheEntry *entry);
static void pgfdw_xact_callback(XactEvent event, void *arg); static void pgfdw_xact_callback(XactEvent event, void *arg);
static void pgfdw_subxact_callback(SubXactEvent event, static void pgfdw_subxact_callback(SubXactEvent event,
@ -568,7 +567,7 @@ configure_remote_session(PGconn *conn)
/* /*
* Convenience subroutine to issue a non-data-returning SQL command to remote * Convenience subroutine to issue a non-data-returning SQL command to remote
*/ */
static void void
do_sql_command(PGconn *conn, const char *sql) do_sql_command(PGconn *conn, const char *sql)
{ {
PGresult *res; PGresult *res;

View File

@ -56,6 +56,7 @@
#include "utils/rel.h" #include "utils/rel.h"
#include "utils/syscache.h" #include "utils/syscache.h"
#include "utils/typcache.h" #include "utils/typcache.h"
#include "commands/tablecmds.h"
/* /*
* Global context for foreign_expr_walker's search of an expression tree. * Global context for foreign_expr_walker's search of an expression tree.
@ -2172,6 +2173,43 @@ deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs)
deparseRelation(buf, rel); deparseRelation(buf, rel);
} }
/*
* Construct a simple "TRUNCATE rel" statement
*/
void
deparseTruncateSql(StringInfo buf,
List *rels,
List *rels_extra,
DropBehavior behavior,
bool restart_seqs)
{
ListCell *lc1,
*lc2;
appendStringInfoString(buf, "TRUNCATE ");
forboth(lc1, rels, lc2, rels_extra)
{
Relation rel = lfirst(lc1);
int extra = lfirst_int(lc2);
if (lc1 != list_head(rels))
appendStringInfoString(buf, ", ");
if (extra & TRUNCATE_REL_CONTEXT_ONLY)
appendStringInfoString(buf, "ONLY ");
deparseRelation(buf, rel);
}
appendStringInfo(buf, " %s IDENTITY",
restart_seqs ? "RESTART" : "CONTINUE");
if (behavior == DROP_RESTRICT)
appendStringInfoString(buf, " RESTRICT");
else if (behavior == DROP_CASCADE)
appendStringInfoString(buf, " CASCADE");
}
/* /*
* Construct name to use for given column, and emit it into buf. * 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. * If it has a column_name FDW option, use that instead of attribute name.

View File

@ -8215,6 +8215,205 @@ select * from rem3;
drop foreign table rem3; drop foreign table rem3;
drop table loc3; drop table loc3;
-- =================================================================== -- ===================================================================
-- test for TRUNCATE
-- ===================================================================
CREATE TABLE tru_rtable0 (id int primary key);
CREATE TABLE tru_rtable1 (id int primary key);
CREATE FOREIGN TABLE tru_ftable (id int)
SERVER loopback OPTIONS (table_name 'tru_rtable0');
INSERT INTO tru_rtable0 (SELECT x FROM generate_series(1,10) x);
CREATE TABLE tru_ptable (id int) PARTITION BY HASH(id);
CREATE TABLE tru_ptable__p0 PARTITION OF tru_ptable
FOR VALUES WITH (MODULUS 2, REMAINDER 0);
CREATE FOREIGN TABLE tru_ftable__p1 PARTITION OF tru_ptable
FOR VALUES WITH (MODULUS 2, REMAINDER 1)
SERVER loopback OPTIONS (table_name 'tru_rtable1');
INSERT INTO tru_ptable (SELECT x FROM generate_series(11,20) x);
CREATE TABLE tru_pk_table(id int primary key);
CREATE TABLE tru_fk_table(fkey int references tru_pk_table(id));
INSERT INTO tru_pk_table (SELECT x FROM generate_series(1,10) x);
INSERT INTO tru_fk_table (SELECT x % 10 + 1 FROM generate_series(5,25) x);
CREATE FOREIGN TABLE tru_pk_ftable (id int)
SERVER loopback OPTIONS (table_name 'tru_pk_table');
CREATE TABLE tru_rtable_parent (id int);
CREATE TABLE tru_rtable_child (id int);
CREATE FOREIGN TABLE tru_ftable_parent (id int)
SERVER loopback OPTIONS (table_name 'tru_rtable_parent');
CREATE FOREIGN TABLE tru_ftable_child () INHERITS (tru_ftable_parent)
SERVER loopback OPTIONS (table_name 'tru_rtable_child');
INSERT INTO tru_rtable_parent (SELECT x FROM generate_series(1,8) x);
INSERT INTO tru_rtable_child (SELECT x FROM generate_series(10, 18) x);
-- normal truncate
SELECT sum(id) FROM tru_ftable; -- 55
sum
-----
55
(1 row)
TRUNCATE tru_ftable;
SELECT count(*) FROM tru_rtable0; -- 0
count
-------
0
(1 row)
SELECT count(*) FROM tru_ftable; -- 0
count
-------
0
(1 row)
-- 'truncatable' option
ALTER SERVER loopback OPTIONS (ADD truncatable 'false');
TRUNCATE tru_ftable; -- error
ERROR: foreign table "tru_ftable" does not allow truncates
ALTER FOREIGN TABLE tru_ftable OPTIONS (ADD truncatable 'true');
TRUNCATE tru_ftable; -- accepted
ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false');
TRUNCATE tru_ftable; -- error
ERROR: foreign table "tru_ftable" does not allow truncates
ALTER SERVER loopback OPTIONS (DROP truncatable);
ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false');
TRUNCATE tru_ftable; -- error
ERROR: foreign table "tru_ftable" does not allow truncates
ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'true');
TRUNCATE tru_ftable; -- accepted
-- partitioned table with both local and foreign tables as partitions
SELECT sum(id) FROM tru_ptable; -- 155
sum
-----
155
(1 row)
TRUNCATE tru_ptable;
SELECT count(*) FROM tru_ptable; -- 0
count
-------
0
(1 row)
SELECT count(*) FROM tru_ptable__p0; -- 0
count
-------
0
(1 row)
SELECT count(*) FROM tru_ftable__p1; -- 0
count
-------
0
(1 row)
SELECT count(*) FROM tru_rtable1; -- 0
count
-------
0
(1 row)
-- 'CASCADE' option
SELECT sum(id) FROM tru_pk_ftable; -- 55
sum
-----
55
(1 row)
TRUNCATE tru_pk_ftable; -- failed by FK reference
ERROR: cannot truncate a table referenced in a foreign key constraint
DETAIL: Table "tru_fk_table" references "tru_pk_table".
HINT: Truncate table "tru_fk_table" at the same time, or use TRUNCATE ... CASCADE.
CONTEXT: remote SQL command: TRUNCATE public.tru_pk_table CONTINUE IDENTITY RESTRICT
TRUNCATE tru_pk_ftable CASCADE;
SELECT count(*) FROM tru_pk_ftable; -- 0
count
-------
0
(1 row)
SELECT count(*) FROM tru_fk_table; -- also truncated,0
count
-------
0
(1 row)
-- truncate two tables at a command
INSERT INTO tru_ftable (SELECT x FROM generate_series(1,8) x);
INSERT INTO tru_pk_ftable (SELECT x FROM generate_series(3,10) x);
SELECT count(*) from tru_ftable; -- 8
count
-------
8
(1 row)
SELECT count(*) from tru_pk_ftable; -- 8
count
-------
8
(1 row)
TRUNCATE tru_ftable, tru_pk_ftable CASCADE;
SELECT count(*) from tru_ftable; -- 0
count
-------
0
(1 row)
SELECT count(*) from tru_pk_ftable; -- 0
count
-------
0
(1 row)
-- truncate with ONLY clause
TRUNCATE ONLY tru_ftable_parent;
SELECT sum(id) FROM tru_ftable_parent; -- 126
sum
-----
126
(1 row)
TRUNCATE tru_ftable_parent;
SELECT count(*) FROM tru_ftable_parent; -- 0
count
-------
0
(1 row)
-- in case when remote table has inherited children
CREATE TABLE tru_rtable0_child () INHERITS (tru_rtable0);
INSERT INTO tru_rtable0 (SELECT x FROM generate_series(5,9) x);
INSERT INTO tru_rtable0_child (SELECT x FROM generate_series(10,14) x);
SELECT sum(id) FROM tru_ftable; -- 95
sum
-----
95
(1 row)
TRUNCATE ONLY tru_ftable; -- truncate only parent portion
SELECT sum(id) FROM tru_ftable; -- 60
sum
-----
60
(1 row)
INSERT INTO tru_rtable0 (SELECT x FROM generate_series(21,25) x);
SELECT sum(id) FROM tru_ftable; -- 175
sum
-----
175
(1 row)
TRUNCATE tru_ftable; -- truncate both of parent and child
SELECT count(*) FROM tru_ftable; -- empty
count
-------
0
(1 row)
-- cleanup
DROP FOREIGN TABLE tru_ftable_parent, tru_ftable_child, tru_pk_ftable,tru_ftable__p1,tru_ftable;
DROP TABLE tru_rtable0, tru_rtable1, tru_ptable, tru_ptable__p0, tru_pk_table, tru_fk_table,
tru_rtable_parent,tru_rtable_child, tru_rtable0_child;
-- ===================================================================
-- test IMPORT FOREIGN SCHEMA -- test IMPORT FOREIGN SCHEMA
-- =================================================================== -- ===================================================================
CREATE SCHEMA import_source; CREATE SCHEMA import_source;
@ -8917,7 +9116,7 @@ DO $d$
END; END;
$d$; $d$;
ERROR: invalid option "password" ERROR: invalid option "password"
HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable, keep_connections HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')" CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different -- If we add a password for our user mapping instead, we should get a different

View File

@ -108,6 +108,7 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
*/ */
if (strcmp(def->defname, "use_remote_estimate") == 0 || if (strcmp(def->defname, "use_remote_estimate") == 0 ||
strcmp(def->defname, "updatable") == 0 || strcmp(def->defname, "updatable") == 0 ||
strcmp(def->defname, "truncatable") == 0 ||
strcmp(def->defname, "async_capable") == 0 || strcmp(def->defname, "async_capable") == 0 ||
strcmp(def->defname, "keep_connections") == 0) strcmp(def->defname, "keep_connections") == 0)
{ {
@ -213,6 +214,9 @@ InitPgFdwOptions(void)
/* updatable is available on both server and table */ /* updatable is available on both server and table */
{"updatable", ForeignServerRelationId, false}, {"updatable", ForeignServerRelationId, false},
{"updatable", ForeignTableRelationId, false}, {"updatable", ForeignTableRelationId, false},
/* truncatable is available on both server and table */
{"truncatable", ForeignServerRelationId, false},
{"truncatable", ForeignTableRelationId, false},
/* fetch_size is available on both server and table */ /* fetch_size is available on both server and table */
{"fetch_size", ForeignServerRelationId, false}, {"fetch_size", ForeignServerRelationId, false},
{"fetch_size", ForeignTableRelationId, false}, {"fetch_size", ForeignTableRelationId, false},

View File

@ -400,6 +400,10 @@ static void postgresExplainForeignModify(ModifyTableState *mtstate,
ExplainState *es); ExplainState *es);
static void postgresExplainDirectModify(ForeignScanState *node, static void postgresExplainDirectModify(ForeignScanState *node,
ExplainState *es); ExplainState *es);
static void postgresExecForeignTruncate(List *rels,
List *rels_extra,
DropBehavior behavior,
bool restart_seqs);
static bool postgresAnalyzeForeignTable(Relation relation, static bool postgresAnalyzeForeignTable(Relation relation,
AcquireSampleRowsFunc *func, AcquireSampleRowsFunc *func,
BlockNumber *totalpages); BlockNumber *totalpages);
@ -588,6 +592,9 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
routine->ExplainForeignModify = postgresExplainForeignModify; routine->ExplainForeignModify = postgresExplainForeignModify;
routine->ExplainDirectModify = postgresExplainDirectModify; routine->ExplainDirectModify = postgresExplainDirectModify;
/* Support function for TRUNCATE */
routine->ExecForeignTruncate = postgresExecForeignTruncate;
/* Support functions for ANALYZE */ /* Support functions for ANALYZE */
routine->AnalyzeForeignTable = postgresAnalyzeForeignTable; routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
@ -2868,6 +2875,102 @@ postgresExplainDirectModify(ForeignScanState *node, ExplainState *es)
} }
} }
/*
* postgresExecForeignTruncate
* Truncate one or more foreign tables
*/
static void
postgresExecForeignTruncate(List *rels,
List *rels_extra,
DropBehavior behavior,
bool restart_seqs)
{
Oid serverid = InvalidOid;
UserMapping *user = NULL;
PGconn *conn = NULL;
StringInfoData sql;
ListCell *lc;
bool server_truncatable = true;
/*
* By default, all postgres_fdw foreign tables are assumed truncatable.
* This can be overridden by a per-server setting, which in turn can be
* overridden by a per-table setting.
*/
foreach(lc, rels)
{
ForeignServer *server = NULL;
Relation rel = lfirst(lc);
ForeignTable *table = GetForeignTable(RelationGetRelid(rel));
ListCell *cell;
bool truncatable;
/*
* First time through, determine whether the foreign server allows
* truncates. Since all specified foreign tables are assumed to belong
* to the same foreign server, this result can be used for other
* foreign tables.
*/
if (!OidIsValid(serverid))
{
serverid = table->serverid;
server = GetForeignServer(serverid);
foreach(cell, server->options)
{
DefElem *defel = (DefElem *) lfirst(cell);
if (strcmp(defel->defname, "truncatable") == 0)
{
server_truncatable = defGetBoolean(defel);
break;
}
}
}
/*
* Confirm that all specified foreign tables belong to the same
* foreign server.
*/
Assert(table->serverid == serverid);
/* Determine whether this foreign table allows truncations */
truncatable = server_truncatable;
foreach(cell, table->options)
{
DefElem *defel = (DefElem *) lfirst(cell);
if (strcmp(defel->defname, "truncatable") == 0)
{
truncatable = defGetBoolean(defel);
break;
}
}
if (!truncatable)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("foreign table \"%s\" does not allow truncates",
RelationGetRelationName(rel))));
}
Assert(OidIsValid(serverid));
/*
* Get connection to the foreign server. Connection manager will
* establish new connection if necessary.
*/
user = GetUserMapping(GetUserId(), serverid);
conn = GetConnection(user, false, NULL);
/* Construct the TRUNCATE command string */
initStringInfo(&sql);
deparseTruncateSql(&sql, rels, rels_extra, behavior, restart_seqs);
/* Issue the TRUNCATE command to remote server */
do_sql_command(conn, sql.data);
pfree(sql.data);
}
/* /*
* estimate_path_cost_size * estimate_path_cost_size

View File

@ -145,6 +145,7 @@ extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt,
extern void ReleaseConnection(PGconn *conn); extern void ReleaseConnection(PGconn *conn);
extern unsigned int GetCursorNumber(PGconn *conn); extern unsigned int GetCursorNumber(PGconn *conn);
extern unsigned int GetPrepStmtNumber(PGconn *conn); extern unsigned int GetPrepStmtNumber(PGconn *conn);
extern void do_sql_command(PGconn *conn, const char *sql);
extern PGresult *pgfdw_get_result(PGconn *conn, const char *query); extern PGresult *pgfdw_get_result(PGconn *conn, const char *query);
extern PGresult *pgfdw_exec_query(PGconn *conn, const char *query, extern PGresult *pgfdw_exec_query(PGconn *conn, const char *query,
PgFdwConnState *state); PgFdwConnState *state);
@ -206,6 +207,11 @@ extern void deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel); extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel);
extern void deparseAnalyzeSql(StringInfo buf, Relation rel, extern void deparseAnalyzeSql(StringInfo buf, Relation rel,
List **retrieved_attrs); List **retrieved_attrs);
extern void deparseTruncateSql(StringInfo buf,
List *rels,
List *rels_extra,
DropBehavior behavior,
bool restart_seqs);
extern void deparseStringLiteral(StringInfo buf, const char *val); 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_rel(EquivalenceClass *ec, RelOptInfo *rel);
extern Expr *find_em_expr_for_input_target(PlannerInfo *root, extern Expr *find_em_expr_for_input_target(PlannerInfo *root,

View File

@ -2351,6 +2351,107 @@ select * from rem3;
drop foreign table rem3; drop foreign table rem3;
drop table loc3; drop table loc3;
-- ===================================================================
-- test for TRUNCATE
-- ===================================================================
CREATE TABLE tru_rtable0 (id int primary key);
CREATE TABLE tru_rtable1 (id int primary key);
CREATE FOREIGN TABLE tru_ftable (id int)
SERVER loopback OPTIONS (table_name 'tru_rtable0');
INSERT INTO tru_rtable0 (SELECT x FROM generate_series(1,10) x);
CREATE TABLE tru_ptable (id int) PARTITION BY HASH(id);
CREATE TABLE tru_ptable__p0 PARTITION OF tru_ptable
FOR VALUES WITH (MODULUS 2, REMAINDER 0);
CREATE FOREIGN TABLE tru_ftable__p1 PARTITION OF tru_ptable
FOR VALUES WITH (MODULUS 2, REMAINDER 1)
SERVER loopback OPTIONS (table_name 'tru_rtable1');
INSERT INTO tru_ptable (SELECT x FROM generate_series(11,20) x);
CREATE TABLE tru_pk_table(id int primary key);
CREATE TABLE tru_fk_table(fkey int references tru_pk_table(id));
INSERT INTO tru_pk_table (SELECT x FROM generate_series(1,10) x);
INSERT INTO tru_fk_table (SELECT x % 10 + 1 FROM generate_series(5,25) x);
CREATE FOREIGN TABLE tru_pk_ftable (id int)
SERVER loopback OPTIONS (table_name 'tru_pk_table');
CREATE TABLE tru_rtable_parent (id int);
CREATE TABLE tru_rtable_child (id int);
CREATE FOREIGN TABLE tru_ftable_parent (id int)
SERVER loopback OPTIONS (table_name 'tru_rtable_parent');
CREATE FOREIGN TABLE tru_ftable_child () INHERITS (tru_ftable_parent)
SERVER loopback OPTIONS (table_name 'tru_rtable_child');
INSERT INTO tru_rtable_parent (SELECT x FROM generate_series(1,8) x);
INSERT INTO tru_rtable_child (SELECT x FROM generate_series(10, 18) x);
-- normal truncate
SELECT sum(id) FROM tru_ftable; -- 55
TRUNCATE tru_ftable;
SELECT count(*) FROM tru_rtable0; -- 0
SELECT count(*) FROM tru_ftable; -- 0
-- 'truncatable' option
ALTER SERVER loopback OPTIONS (ADD truncatable 'false');
TRUNCATE tru_ftable; -- error
ALTER FOREIGN TABLE tru_ftable OPTIONS (ADD truncatable 'true');
TRUNCATE tru_ftable; -- accepted
ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false');
TRUNCATE tru_ftable; -- error
ALTER SERVER loopback OPTIONS (DROP truncatable);
ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false');
TRUNCATE tru_ftable; -- error
ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'true');
TRUNCATE tru_ftable; -- accepted
-- partitioned table with both local and foreign tables as partitions
SELECT sum(id) FROM tru_ptable; -- 155
TRUNCATE tru_ptable;
SELECT count(*) FROM tru_ptable; -- 0
SELECT count(*) FROM tru_ptable__p0; -- 0
SELECT count(*) FROM tru_ftable__p1; -- 0
SELECT count(*) FROM tru_rtable1; -- 0
-- 'CASCADE' option
SELECT sum(id) FROM tru_pk_ftable; -- 55
TRUNCATE tru_pk_ftable; -- failed by FK reference
TRUNCATE tru_pk_ftable CASCADE;
SELECT count(*) FROM tru_pk_ftable; -- 0
SELECT count(*) FROM tru_fk_table; -- also truncated,0
-- truncate two tables at a command
INSERT INTO tru_ftable (SELECT x FROM generate_series(1,8) x);
INSERT INTO tru_pk_ftable (SELECT x FROM generate_series(3,10) x);
SELECT count(*) from tru_ftable; -- 8
SELECT count(*) from tru_pk_ftable; -- 8
TRUNCATE tru_ftable, tru_pk_ftable CASCADE;
SELECT count(*) from tru_ftable; -- 0
SELECT count(*) from tru_pk_ftable; -- 0
-- truncate with ONLY clause
TRUNCATE ONLY tru_ftable_parent;
SELECT sum(id) FROM tru_ftable_parent; -- 126
TRUNCATE tru_ftable_parent;
SELECT count(*) FROM tru_ftable_parent; -- 0
-- in case when remote table has inherited children
CREATE TABLE tru_rtable0_child () INHERITS (tru_rtable0);
INSERT INTO tru_rtable0 (SELECT x FROM generate_series(5,9) x);
INSERT INTO tru_rtable0_child (SELECT x FROM generate_series(10,14) x);
SELECT sum(id) FROM tru_ftable; -- 95
TRUNCATE ONLY tru_ftable; -- truncate only parent portion
SELECT sum(id) FROM tru_ftable; -- 60
INSERT INTO tru_rtable0 (SELECT x FROM generate_series(21,25) x);
SELECT sum(id) FROM tru_ftable; -- 175
TRUNCATE tru_ftable; -- truncate both of parent and child
SELECT count(*) FROM tru_ftable; -- empty
-- cleanup
DROP FOREIGN TABLE tru_ftable_parent, tru_ftable_child, tru_pk_ftable,tru_ftable__p1,tru_ftable;
DROP TABLE tru_rtable0, tru_rtable1, tru_ptable, tru_ptable__p0, tru_pk_table, tru_fk_table,
tru_rtable_parent,tru_rtable_child, tru_rtable0_child;
-- =================================================================== -- ===================================================================
-- test IMPORT FOREIGN SCHEMA -- test IMPORT FOREIGN SCHEMA
-- =================================================================== -- ===================================================================

View File

@ -1065,6 +1065,67 @@ EndDirectModify(ForeignScanState *node);
</sect2> </sect2>
<sect2 id="fdw-callbacks-truncate">
<title>FDW Routines for <command>TRUNCATE</command></title>
<para>
<programlisting>
void
ExecForeignTruncate(List *rels, List *rels_extra,
DropBehavior behavior, bool restart_seqs);
</programlisting>
Truncate a set of foreign tables specified in <literal>rels</literal>.
This function is called when <xref linkend="sql-truncate"/> is executed
on foreign tables. <literal>rels</literal> is the list of
<structname>Relation</structname> data structure that indicates
a foreign table to truncate. <literal>rels_extra</literal> the list of
<literal>int</literal> value, which delivers extra information about
a foreign table to truncate. Possible values are
<literal>TRUNCATE_REL_CONTEXT_NORMAL</literal>, which means that
the foreign table is specified WITHOUT <literal>ONLY</literal> clause
in <command>TRUNCATE</command>,
<literal>TRUNCATE_REL_CONTEXT_ONLY</literal>, which means that
the foreign table is specified WITH <literal>ONLY</literal> clause,
and <literal>TRUNCATE_REL_CONTEXT_CASCADING</literal>,
which means that the foreign table is not specified explicitly,
but will be truncated due to dependency (for example, partition table).
There is one-to-one mapping between <literal>rels</literal> and
<literal>rels_extra</literal>. The number of entries is the same
between the two lists.
</para>
<para>
<literal>behavior</literal> defines how foreign tables should
be truncated, using as possible values <literal>DROP_RESTRICT</literal>,
which means that <literal>RESTRICT</literal> option is specified,
and <literal>DROP_CASCADE</literal>, which means that
<literal>CASCADE</literal> option is specified, in
<command>TRUNCATE</command> command.
</para>
<para>
<literal>restart_seqs</literal> is set to <literal>true</literal>
if <literal>RESTART IDENTITY</literal> option is specified in
<command>TRUNCATE</command> command. It is <literal>false</literal>
if <literal>CONTINUE IDENTITY</literal> option is specified.
</para>
<para>
<command>TRUNCATE</command> invokes
<function>ExecForeignTruncate</function> once per foreign server
that foreign tables to truncate belong to. This means that all foreign
tables included in <literal>rels</literal> must belong to the same
server.
</para>
<para>
If the <function>ExecForeignTruncate</function> pointer is set to
<literal>NULL</literal>, attempts to truncate foreign tables will
fail with an error message.
</para>
</sect2>
<sect2 id="fdw-callbacks-row-locking"> <sect2 id="fdw-callbacks-row-locking">
<title>FDW Routines for Row Locking</title> <title>FDW Routines for Row Locking</title>

View File

@ -63,9 +63,10 @@
<para> <para>
Now you need only <command>SELECT</command> from a foreign table to access Now you need only <command>SELECT</command> from a foreign table to access
the data stored in its underlying remote table. You can also modify the data stored in its underlying remote table. You can also modify
the remote table using <command>INSERT</command>, <command>UPDATE</command>, or the remote table using <command>INSERT</command>, <command>UPDATE</command>,
<command>DELETE</command>. (Of course, the remote user you have specified <command>DELETE</command>, or <command>TRUNCATE</command>.
in your user mapping must have privileges to do these things.) (Of course, the remote user you have specified in your user mapping must
have privileges to do these things.)
</para> </para>
<para> <para>
@ -436,6 +437,31 @@ OPTIONS (ADD password_required 'false');
</variablelist> </variablelist>
</sect3> </sect3>
<sect3>
<title>Truncatability Options</title>
<para>
By default all foreign tables using <filename>postgres_fdw</filename> are assumed
to be truncatable. This may be overridden using the following option:
</para>
<variablelist>
<varlistentry>
<term><literal>truncatable</literal></term>
<listitem>
<para>
This option controls whether <filename>postgres_fdw</filename> allows
foreign tables to be truncated using <command>TRUNCATE</command>
command. It can be specified for a foreign table or a foreign server.
A table-level option overrides a server-level option.
The default is <literal>true</literal>.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect3>
<sect3> <sect3>
<title>Importing Options</title> <title>Importing Options</title>

View File

@ -172,9 +172,9 @@ TRUNCATE [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [
</para> </para>
<para> <para>
<command>TRUNCATE</command> is not currently supported for foreign tables. <command>TRUNCATE</command> can be used for foreign tables if
This implies that if a specified table has any descendant tables that are the foreign data wrapper supports, for instance,
foreign, the command will fail. see <xref linkend="postgres-fdw"/>.
</para> </para>
</refsect1> </refsect1>

View File

@ -59,6 +59,7 @@
#include "commands/typecmds.h" #include "commands/typecmds.h"
#include "commands/user.h" #include "commands/user.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "foreign/fdwapi.h"
#include "foreign/foreign.h" #include "foreign/foreign.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
@ -310,6 +311,21 @@ struct DropRelationCallbackState
#define ATT_FOREIGN_TABLE 0x0020 #define ATT_FOREIGN_TABLE 0x0020
#define ATT_PARTITIONED_INDEX 0x0040 #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 * Partition tables are expected to be dropped when the parent partitioned
* table gets dropped. Hence for partitioning we use AUTO dependency. * 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 * This is a multi-relation truncate. We first open and grab exclusive
* lock on all relations involved, checking permissions and otherwise * 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 * relations having FK references to the targeted relations are automatically
* added to the group; in RESTRICT mode, we check that all FK references are * 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 * internal to the group that's being truncated. Finally all the relations
@ -1600,6 +1619,7 @@ ExecuteTruncate(TruncateStmt *stmt)
{ {
List *rels = NIL; List *rels = NIL;
List *relids = NIL; List *relids = NIL;
List *relids_extra = NIL;
List *relids_logged = NIL; List *relids_logged = NIL;
ListCell *cell; ListCell *cell;
@ -1636,6 +1656,9 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel); rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid); 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 */ /* Log this relation only if needed for logical decoding */
if (RelationIsLogicallyLogged(rel)) if (RelationIsLogicallyLogged(rel))
relids_logged = lappend_oid(relids_logged, myrelid); relids_logged = lappend_oid(relids_logged, myrelid);
@ -1683,6 +1706,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel); rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid); relids = lappend_oid(relids, childrelid);
relids_extra = lappend_int(relids_extra,
TRUNCATE_REL_CONTEXT_CASCADING);
/* Log this relation only if needed for logical decoding */ /* Log this relation only if needed for logical decoding */
if (RelationIsLogicallyLogged(rel)) if (RelationIsLogicallyLogged(rel))
relids_logged = lappend_oid(relids_logged, childrelid); 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."))); 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); stmt->behavior, stmt->restart_seqs);
/* And close the rels */ /* And close the rels */
@ -1716,21 +1741,28 @@ ExecuteTruncate(TruncateStmt *stmt)
* *
* explicit_rels is the list of Relations to truncate that the command * explicit_rels is the list of Relations to truncate that the command
* specified. relids is the list of Oids corresponding to explicit_rels. * specified. relids is the list of Oids corresponding to explicit_rels.
* relids_logged is the list of Oids (a subset of relids) that require * relids_extra is the list of integer values that deliver extra information
* WAL-logging. This is all a bit redundant, but the existing callers have * about relations in explicit_rels. relids_logged is the list of Oids
* this information handy in this form. * (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 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) DropBehavior behavior, bool restart_seqs)
{ {
List *rels; List *rels;
List *seq_relids = NIL; List *seq_relids = NIL;
HTAB *ft_htab = NULL;
EState *estate; EState *estate;
ResultRelInfo *resultRelInfos; ResultRelInfo *resultRelInfos;
ResultRelInfo *resultRelInfo; ResultRelInfo *resultRelInfo;
SubTransactionId mySubid; SubTransactionId mySubid;
ListCell *cell; ListCell *cell;
ListCell *lc1,
*lc2;
Oid *logrelids; Oid *logrelids;
/* /*
@ -1768,6 +1800,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
truncate_check_activity(rel); truncate_check_activity(rel);
rels = lappend(rels, rel); rels = lappend(rels, rel);
relids = lappend_oid(relids, relid); relids = lappend_oid(relids, relid);
relids_extra = lappend_int(relids_extra,
TRUNCATE_REL_CONTEXT_CASCADING);
/* Log this relation only if needed for logical decoding */ /* Log this relation only if needed for logical decoding */
if (RelationIsLogicallyLogged(rel)) if (RelationIsLogicallyLogged(rel))
relids_logged = lappend_oid(relids_logged, relid); relids_logged = lappend_oid(relids_logged, relid);
@ -1868,14 +1902,63 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
*/ */
mySubid = GetCurrentSubTransactionId(); 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 */ /* Skip partitioned tables as there is nothing to do */
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
continue; 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 * Normally, we need a transaction-safe truncation here. However, if
* the table was either created in the current (sub)transaction or has * 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); 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. * 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); 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
* the latter are only being included here for the following checks; no * data wrappers supporting TRUNCATE and partitioned tables (although, the
* physical truncation will occur in their case.) * latter are only being included here for the following checks; no
* physical truncation will occur in their case.).
*/ */
if (reltuple->relkind != RELKIND_RELATION && if (reltuple->relkind == RELKIND_FOREIGN_TABLE)
reltuple->relkind != RELKIND_PARTITIONED_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, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", relname))); errmsg("\"%s\" is not a table", relname)));

View File

@ -1795,6 +1795,7 @@ apply_handle_truncate(StringInfo s)
List *rels = NIL; List *rels = NIL;
List *part_rels = NIL; List *part_rels = NIL;
List *relids = NIL; List *relids = NIL;
List *relids_extra = NIL;
List *relids_logged = NIL; List *relids_logged = NIL;
ListCell *lc; ListCell *lc;
@ -1824,6 +1825,7 @@ apply_handle_truncate(StringInfo s)
remote_rels = lappend(remote_rels, rel); remote_rels = lappend(remote_rels, rel);
rels = lappend(rels, rel->localrel); rels = lappend(rels, rel->localrel);
relids = lappend_oid(relids, rel->localreloid); relids = lappend_oid(relids, rel->localreloid);
relids_extra = lappend_int(relids_extra, TRUNCATE_REL_CONTEXT_NORMAL);
if (RelationIsLogicallyLogged(rel->localrel)) if (RelationIsLogicallyLogged(rel->localrel))
relids_logged = lappend_oid(relids_logged, rel->localreloid); relids_logged = lappend_oid(relids_logged, rel->localreloid);
@ -1862,6 +1864,7 @@ apply_handle_truncate(StringInfo s)
rels = lappend(rels, childrel); rels = lappend(rels, childrel);
part_rels = lappend(part_rels, childrel); part_rels = lappend(part_rels, childrel);
relids = lappend_oid(relids, childrelid); relids = lappend_oid(relids, childrelid);
relids_extra = lappend_int(relids_extra, TRUNCATE_REL_CONTEXT_CASCADING);
/* Log this relation only if needed for logical decoding */ /* Log this relation only if needed for logical decoding */
if (RelationIsLogicallyLogged(childrel)) if (RelationIsLogicallyLogged(childrel))
relids_logged = lappend_oid(relids_logged, childrelid); 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 * to replaying changes without further cascading. This might be later
* changeable with a user specified option. * 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) foreach(lc, remote_rels)
{ {
LogicalRepRelMapEntry *rel = lfirst(lc); LogicalRepRelMapEntry *rel = lfirst(lc);

View File

@ -21,6 +21,16 @@
#include "storage/lock.h" #include "storage/lock.h"
#include "utils/relcache.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 */ 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 CheckTableNotInUse(Relation rel, const char *stmt);
extern void ExecuteTruncate(TruncateStmt *stmt); extern void ExecuteTruncate(TruncateStmt *stmt);
extern void ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, extern void ExecuteTruncateGuts(List *explicit_rels,
DropBehavior behavior, bool restart_seqs); List *relids,
List *relids_extra,
List *relids_logged,
DropBehavior behavior,
bool restart_seqs);
extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass); extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass);

View File

@ -160,6 +160,11 @@ typedef bool (*AnalyzeForeignTable_function) (Relation relation,
typedef List *(*ImportForeignSchema_function) (ImportForeignSchemaStmt *stmt, typedef List *(*ImportForeignSchema_function) (ImportForeignSchemaStmt *stmt,
Oid serverOid); Oid serverOid);
typedef void (*ExecForeignTruncate_function) (List *rels,
List *rels_extra,
DropBehavior behavior,
bool restart_seqs);
typedef Size (*EstimateDSMForeignScan_function) (ForeignScanState *node, typedef Size (*EstimateDSMForeignScan_function) (ForeignScanState *node,
ParallelContext *pcxt); ParallelContext *pcxt);
typedef void (*InitializeDSMForeignScan_function) (ForeignScanState *node, typedef void (*InitializeDSMForeignScan_function) (ForeignScanState *node,
@ -255,6 +260,9 @@ typedef struct FdwRoutine
/* Support functions for IMPORT FOREIGN SCHEMA */ /* Support functions for IMPORT FOREIGN SCHEMA */
ImportForeignSchema_function ImportForeignSchema; ImportForeignSchema_function ImportForeignSchema;
/* Support functions for TRUNCATE */
ExecForeignTruncate_function ExecForeignTruncate;
/* Support functions for parallelism under Gather node */ /* Support functions for parallelism under Gather node */
IsForeignScanParallelSafe_function IsForeignScanParallelSafe; IsForeignScanParallelSafe_function IsForeignScanParallelSafe;
EstimateDSMForeignScan_function EstimateDSMForeignScan; EstimateDSMForeignScan_function EstimateDSMForeignScan;

View File

@ -634,7 +634,8 @@ typedef struct ViewOptions
* WAL stream. * WAL stream.
* *
* We don't log information for unlogged tables (since they don't WAL log * 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* * it would complicate decoding slightly for little gain). Note that we *do*
* log information for user defined catalog tables since they presumably are * log information for user defined catalog tables since they presumably are
* interesting to the user... * interesting to the user...
@ -642,6 +643,7 @@ typedef struct ViewOptions
#define RelationIsLogicallyLogged(relation) \ #define RelationIsLogicallyLogged(relation) \
(XLogLogicalInfoActive() && \ (XLogLogicalInfoActive() && \
RelationNeedsWAL(relation) && \ RelationNeedsWAL(relation) && \
(relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE && \
!IsCatalogRelation(relation)) !IsCatalogRelation(relation))
/* routines in utils/cache/relcache.c */ /* routines in utils/cache/relcache.c */

View File

@ -1807,9 +1807,9 @@ Inherits: fd_pt1
-- TRUNCATE doesn't work on foreign tables, either directly or recursively -- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE ft2; -- ERROR TRUNCATE ft2; -- ERROR
ERROR: "ft2" is not a table ERROR: foreign-data wrapper "dummy" has no handler
TRUNCATE fd_pt1; -- ERROR TRUNCATE fd_pt1; -- ERROR
ERROR: "ft2" is not a table ERROR: foreign-data wrapper "dummy" has no handler
DROP TABLE fd_pt1 CASCADE; DROP TABLE fd_pt1 CASCADE;
NOTICE: drop cascades to foreign table ft2 NOTICE: drop cascades to foreign table ft2
-- IMPORT FOREIGN SCHEMA -- 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); 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 doesn't work on foreign tables, either directly or recursively
TRUNCATE fd_pt2_1; -- ERROR 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 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 FOREIGN TABLE fd_pt2_1;
DROP TABLE fd_pt2; DROP TABLE fd_pt2;
-- foreign table cannot be part of partition tree made of temporary -- foreign table cannot be part of partition tree made of temporary

View File

@ -704,6 +704,7 @@ ForeignScanState
ForeignServer ForeignServer
ForeignServerInfo ForeignServerInfo
ForeignTable ForeignTable
ForeignTruncateInfo
ForkNumber ForkNumber
FormData_pg_aggregate FormData_pg_aggregate
FormData_pg_am FormData_pg_am