forked from huawei/openGauss-server
postgres_fdw.
This commit is contained in:
parent
0feea64d09
commit
7e2384c323
|
|
@ -58,6 +58,7 @@ else
|
|||
install:
|
||||
$(MAKE) install_mysql_fdw
|
||||
$(MAKE) install_oracle_fdw
|
||||
$(MAKE) -C contrib/postgres_fdw $@
|
||||
$(MAKE) -C contrib/pg_stat_statements $@
|
||||
+@echo "openGauss installation complete."
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
# Generated subdirectories
|
||||
/log/
|
||||
/results/
|
||||
/tmp_check/
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# contrib/postgres_fdw/Makefile
|
||||
|
||||
MODULE_big = postgres_fdw
|
||||
OBJS = postgres_fdw.o option.o deparse.o connection.o
|
||||
|
||||
PG_CPPFLAGS = -I$(libpq_srcdir)
|
||||
SHLIB_LINK_INTERNAL = $(libpq)
|
||||
|
||||
EXTENSION = postgres_fdw
|
||||
DATA = postgres_fdw--1.0.sql
|
||||
|
||||
REGRESS = postgres_fdw
|
||||
|
||||
# the db name is hard-coded in the tests
|
||||
override USE_MODULE_DB =
|
||||
|
||||
ifdef USE_PGXS
|
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
||||
else
|
||||
SHLIB_PREREQS = submake-libpq
|
||||
subdir = contrib/postgres_fdw
|
||||
top_builddir = ../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
include $(top_srcdir)/contrib/contrib-global.mk
|
||||
endif
|
||||
|
||||
exclude_option=-fPIE
|
||||
override CPPFLAGS := $(filter-out $(exclude_option),$(CPPFLAGS))
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,271 @@
|
|||
/* -------------------------------------------------------------------------
|
||||
*
|
||||
* option.c
|
||||
* FDW option handling for postgres_fdw
|
||||
*
|
||||
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
* Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* contrib/postgres_fdw/option.c
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "postgres_fdw.h"
|
||||
|
||||
#include "access/reloptions.h"
|
||||
#include "catalog/pg_foreign_server.h"
|
||||
#include "catalog/pg_foreign_table.h"
|
||||
#include "catalog/pg_user_mapping.h"
|
||||
#include "commands/defrem.h"
|
||||
|
||||
|
||||
/*
|
||||
* Describes the valid options for objects that this wrapper uses.
|
||||
*/
|
||||
typedef struct PgFdwOption {
|
||||
const char *keyword;
|
||||
Oid optcontext; /* OID of catalog in which option may appear */
|
||||
bool is_libpq_opt; /* true if it's used in libpq */
|
||||
} PgFdwOption;
|
||||
|
||||
/*
|
||||
* Valid options for postgres_fdw.
|
||||
* Allocated and filled in InitPgFdwOptions.
|
||||
*/
|
||||
static PgFdwOption *postgres_fdw_options;
|
||||
|
||||
/*
|
||||
* Valid options for libpq.
|
||||
* Allocated and filled in InitPgFdwOptions.
|
||||
*/
|
||||
static PQconninfoOption *libpq_options;
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
*/
|
||||
static void InitPgFdwOptions(void);
|
||||
static bool is_valid_option(const char *keyword, Oid context);
|
||||
static bool is_libpq_option(const char *keyword);
|
||||
|
||||
|
||||
/*
|
||||
* Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
|
||||
* USER MAPPING or FOREIGN TABLE that uses postgres_fdw.
|
||||
*
|
||||
* Raise an ERROR if the option or its value is considered invalid.
|
||||
*/
|
||||
PG_FUNCTION_INFO_V1(postgres_fdw_validator);
|
||||
extern "C" Datum postgres_fdw_validator(PG_FUNCTION_ARGS);
|
||||
|
||||
Datum postgres_fdw_validator(PG_FUNCTION_ARGS)
|
||||
{
|
||||
List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
|
||||
Oid catalog = PG_GETARG_OID(1);
|
||||
ListCell *cell = NULL;
|
||||
|
||||
/* Build our options lists if we didn't yet. */
|
||||
InitPgFdwOptions();
|
||||
|
||||
/*
|
||||
* Check that only options supported by postgres_fdw, and allowed for the
|
||||
* current object type, are given.
|
||||
*/
|
||||
foreach (cell, options_list) {
|
||||
DefElem *def = (DefElem *)lfirst(cell);
|
||||
|
||||
if (!is_valid_option(def->defname, catalog)) {
|
||||
/*
|
||||
* Unknown option specified, complain about it. Provide a hint
|
||||
* with list of valid options for the object.
|
||||
*/
|
||||
PgFdwOption *opt = NULL;
|
||||
StringInfoData buf;
|
||||
|
||||
initStringInfo(&buf);
|
||||
for (opt = postgres_fdw_options; opt->keyword; opt++) {
|
||||
if (catalog == opt->optcontext) {
|
||||
appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->keyword);
|
||||
}
|
||||
}
|
||||
|
||||
ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("invalid option \"%s\"", def->defname),
|
||||
errhint("Valid options in this context are: %s", buf.data)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate option value, when we can do so without any context.
|
||||
*/
|
||||
if (strcmp(def->defname, "use_remote_estimate") == 0 || strcmp(def->defname, "updatable") == 0) {
|
||||
/* these accept only boolean values */
|
||||
(void)defGetBoolean(def);
|
||||
} else if (strcmp(def->defname, "fdw_startup_cost") == 0 || strcmp(def->defname, "fdw_tuple_cost") == 0) {
|
||||
char *endp = NULL;
|
||||
/* these must have a non-negative numeric value */
|
||||
double val = strtod(defGetString(def), &endp);
|
||||
if (*endp || val < 0) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR), errmsg("%s requires a non-negative numeric value", def->defname)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize option lists.
|
||||
*/
|
||||
static void InitPgFdwOptions(void)
|
||||
{
|
||||
PQconninfoOption *lopt = NULL;
|
||||
|
||||
/* non-libpq FDW-specific FDW options */
|
||||
static const PgFdwOption non_libpq_options[] = {
|
||||
{"schema_name", ForeignTableRelationId, false},
|
||||
{"table_name", ForeignTableRelationId, false},
|
||||
{"column_name", AttributeRelationId, false},
|
||||
/* use_remote_estimate is available on both server and table */
|
||||
{"use_remote_estimate", ForeignServerRelationId, false},
|
||||
{"use_remote_estimate", ForeignTableRelationId, false},
|
||||
/* cost factors */
|
||||
{"fdw_startup_cost", ForeignServerRelationId, false},
|
||||
{"fdw_tuple_cost", ForeignServerRelationId, false},
|
||||
/* updatable is available on both server and table */
|
||||
{"updatable", ForeignServerRelationId, false},
|
||||
{"updatable", ForeignTableRelationId, false},
|
||||
{NULL, InvalidOid, false}
|
||||
};
|
||||
|
||||
/* Prevent redundant initialization. */
|
||||
if (postgres_fdw_options) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get list of valid libpq options.
|
||||
*
|
||||
* To avoid unnecessary work, we get the list once and use it throughout
|
||||
* the lifetime of this backend process. We don't need to care about
|
||||
* memory context issues, because PQconndefaults allocates with malloc.
|
||||
*/
|
||||
libpq_options = PQconndefaults();
|
||||
if (!libpq_options) { /* assume reason for failure is OOM */
|
||||
ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("out of memory"),
|
||||
errdetail("could not get libpq's default connection options")));
|
||||
}
|
||||
|
||||
/* Count how many libpq options are available. */
|
||||
unsigned int num_libpq_opts = 0;
|
||||
for (lopt = libpq_options; lopt->keyword; lopt++) {
|
||||
num_libpq_opts++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct an array which consists of all valid options for
|
||||
* postgres_fdw, by appending FDW-specific options to libpq options.
|
||||
*
|
||||
* We use plain malloc here to allocate postgres_fdw_options because it
|
||||
* lives as long as the backend process does. Besides, keeping
|
||||
* libpq_options in memory allows us to avoid copying every keyword
|
||||
* string.
|
||||
*/
|
||||
postgres_fdw_options = (PgFdwOption *)malloc(sizeof(PgFdwOption) * num_libpq_opts + sizeof(non_libpq_options));
|
||||
if (postgres_fdw_options == NULL) {
|
||||
ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("out of memory")));
|
||||
}
|
||||
|
||||
PgFdwOption* popt = postgres_fdw_options;
|
||||
for (lopt = libpq_options; lopt->keyword; lopt++) {
|
||||
/* Hide debug options, as well as settings we override internally. */
|
||||
if (strchr(lopt->dispchar, 'D') || strcmp(lopt->keyword, "fallback_application_name") == 0 ||
|
||||
strcmp(lopt->keyword, "client_encoding") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We don't have to copy keyword string, as described above. */
|
||||
popt->keyword = lopt->keyword;
|
||||
|
||||
/*
|
||||
* "user" and any secret options are allowed only on user mappings.
|
||||
* Everything else is a server option.
|
||||
*/
|
||||
if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*')) {
|
||||
popt->optcontext = UserMappingRelationId;
|
||||
} else {
|
||||
popt->optcontext = ForeignServerRelationId;
|
||||
}
|
||||
popt->is_libpq_opt = true;
|
||||
|
||||
popt++;
|
||||
}
|
||||
|
||||
/* Append FDW-specific options and dummy terminator. */
|
||||
int rc = memcpy_s(popt, sizeof(non_libpq_options), non_libpq_options, sizeof(non_libpq_options));
|
||||
securec_check(rc, "", "");
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether the given option is one of the valid postgres_fdw options.
|
||||
* context is the Oid of the catalog holding the object the option is for.
|
||||
*/
|
||||
static bool is_valid_option(const char *keyword, Oid context)
|
||||
{
|
||||
PgFdwOption *opt = NULL;
|
||||
|
||||
Assert(postgres_fdw_options); /* must be initialized already */
|
||||
|
||||
for (opt = postgres_fdw_options; opt->keyword; opt++) {
|
||||
if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether the given option is one of the valid libpq options.
|
||||
*/
|
||||
static bool is_libpq_option(const char *keyword)
|
||||
{
|
||||
PgFdwOption *opt = NULL;
|
||||
|
||||
Assert(postgres_fdw_options); /* must be initialized already */
|
||||
|
||||
for (opt = postgres_fdw_options; opt->keyword; opt++) {
|
||||
if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate key-value arrays which include only libpq options from the
|
||||
* given list (which can contain any kind of options). Caller must have
|
||||
* allocated large-enough arrays. Returns number of options found.
|
||||
*/
|
||||
int ExtractConnectionOptions(List *defelems, const char **keywords, const char **values)
|
||||
{
|
||||
ListCell *lc = NULL;
|
||||
|
||||
/* Build our options lists if we didn't yet. */
|
||||
InitPgFdwOptions();
|
||||
|
||||
int i = 0;
|
||||
foreach (lc, defelems) {
|
||||
DefElem *d = (DefElem *)lfirst(lc);
|
||||
|
||||
if (is_libpq_option(d->defname)) {
|
||||
keywords[i] = d->defname;
|
||||
values[i] = defGetString(d);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/* contrib/postgres_fdw/postgres_fdw--1.0.sql */
|
||||
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "CREATE EXTENSION postgres_fdw" to load this file. \quit
|
||||
|
||||
CREATE FUNCTION postgres_fdw_handler()
|
||||
RETURNS fdw_handler
|
||||
AS 'MODULE_PATHNAME'
|
||||
LANGUAGE C STRICT;
|
||||
|
||||
CREATE FUNCTION postgres_fdw_validator(text[], oid)
|
||||
RETURNS void
|
||||
AS 'MODULE_PATHNAME'
|
||||
LANGUAGE C STRICT;
|
||||
|
||||
CREATE FOREIGN DATA WRAPPER postgres_fdw
|
||||
HANDLER postgres_fdw_handler
|
||||
VALIDATOR postgres_fdw_validator;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# postgres_fdw extension
|
||||
comment = 'foreign-data wrapper for remote PostgreSQL servers'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/postgres_fdw'
|
||||
relocatable = true
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,58 @@
|
|||
/* -------------------------------------------------------------------------
|
||||
*
|
||||
* postgres_fdw.h
|
||||
* Foreign-data wrapper for remote PostgreSQL servers
|
||||
*
|
||||
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
* Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* contrib/postgres_fdw/postgres_fdw.h
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef POSTGRES_FDW_H
|
||||
#define POSTGRES_FDW_H
|
||||
|
||||
#include "foreign/foreign.h"
|
||||
#include "lib/stringinfo.h"
|
||||
#include "nodes/relation.h"
|
||||
#include "utils/rel.h"
|
||||
|
||||
#include "libpq/libpq-fe.h"
|
||||
|
||||
/* in postgres_fdw.c */
|
||||
extern int set_transmission_modes(void);
|
||||
extern void reset_transmission_modes(int nestlevel);
|
||||
|
||||
/* in connection.c */
|
||||
extern PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool will_prep_stmt);
|
||||
extern void ReleaseConnection(PGconn *conn);
|
||||
extern unsigned int GetCursorNumber(PGconn *conn);
|
||||
extern unsigned int GetPrepStmtNumber(PGconn *conn);
|
||||
extern PGresult *pgfdw_get_result(PGconn *conn, const char *query);
|
||||
extern PGresult *pgfdw_exec_query(PGconn *conn, const char *query);
|
||||
extern void pgfdw_report_error(int elevel, PGresult *res, PGconn *conn, bool clear, const char *sql);
|
||||
|
||||
/* in option.c */
|
||||
extern int ExtractConnectionOptions(List *defelems, const char **keywords, const char **values);
|
||||
|
||||
/* in deparse.c */
|
||||
extern void classifyConditions(PlannerInfo *root, RelOptInfo *baserel, List *input_conds, List **remote_conds,
|
||||
List **local_conds);
|
||||
extern bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr);
|
||||
extern void deparseSelectSql(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, Bitmapset *attrs_used,
|
||||
List **retrieved_attrs);
|
||||
extern void appendWhereClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, List *exprs, bool is_first,
|
||||
List **params);
|
||||
extern void deparseInsertSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, List *targetAttrs,
|
||||
List *returningList, List **retrieved_attrs);
|
||||
extern void deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, List *targetAttrs,
|
||||
List *returningList, List **retrieved_attrs);
|
||||
extern void deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, List *returningList,
|
||||
List **retrieved_attrs);
|
||||
extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel);
|
||||
extern void deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs);
|
||||
|
||||
#endif /* POSTGRES_FDW_H */
|
||||
|
||||
|
|
@ -0,0 +1,713 @@
|
|||
-- ===================================================================
|
||||
-- create FDW objects
|
||||
-- ===================================================================
|
||||
|
||||
CREATE EXTENSION postgres_fdw;
|
||||
|
||||
CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw;
|
||||
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
|
||||
OPTIONS (dbname 'contrib_regression');
|
||||
|
||||
CREATE USER MAPPING FOR public SERVER testserver1
|
||||
OPTIONS (user 'value', password 'value');
|
||||
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
|
||||
|
||||
-- ===================================================================
|
||||
-- create objects used through FDW loopback server
|
||||
-- ===================================================================
|
||||
CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz');
|
||||
CREATE SCHEMA "S 1";
|
||||
CREATE TABLE "S 1"."T 1" (
|
||||
"C 1" int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
c3 text,
|
||||
c4 timestamptz,
|
||||
c5 timestamp,
|
||||
c6 varchar(10),
|
||||
c7 char(10),
|
||||
c8 user_enum,
|
||||
CONSTRAINT t1_pkey PRIMARY KEY ("C 1")
|
||||
);
|
||||
CREATE TABLE "S 1"."T 2" (
|
||||
c1 int NOT NULL,
|
||||
c2 text,
|
||||
CONSTRAINT t2_pkey PRIMARY KEY (c1)
|
||||
);
|
||||
|
||||
INSERT INTO "S 1"."T 1"
|
||||
SELECT id,
|
||||
id % 10,
|
||||
to_char(id, 'FM00000'),
|
||||
'1970-01-01'::timestamptz + ((id % 100) || ' days')::interval,
|
||||
'1970-01-01'::timestamp + ((id % 100) || ' days')::interval,
|
||||
id % 10,
|
||||
id % 10,
|
||||
'foo'::user_enum
|
||||
FROM generate_series(1, 1000) id;
|
||||
INSERT INTO "S 1"."T 2"
|
||||
SELECT id,
|
||||
'AAA' || to_char(id, 'FM000')
|
||||
FROM generate_series(1, 100) id;
|
||||
|
||||
ANALYZE "S 1"."T 1";
|
||||
ANALYZE "S 1"."T 2";
|
||||
|
||||
-- ===================================================================
|
||||
-- create foreign tables
|
||||
-- ===================================================================
|
||||
CREATE FOREIGN TABLE ft1 (
|
||||
c0 int,
|
||||
c1 int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
c3 text,
|
||||
c4 timestamptz,
|
||||
c5 timestamp,
|
||||
c6 varchar(10),
|
||||
c7 char(10) default 'ft1',
|
||||
c8 user_enum
|
||||
) SERVER loopback;
|
||||
ALTER FOREIGN TABLE ft1 DROP COLUMN c0;
|
||||
|
||||
CREATE FOREIGN TABLE ft2 (
|
||||
c1 int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
cx int,
|
||||
c3 text,
|
||||
c4 timestamptz,
|
||||
c5 timestamp,
|
||||
c6 varchar(10),
|
||||
c7 char(10) default 'ft2',
|
||||
c8 user_enum
|
||||
) SERVER loopback;
|
||||
ALTER FOREIGN TABLE ft2 DROP COLUMN cx;
|
||||
|
||||
-- ===================================================================
|
||||
-- tests for validator
|
||||
-- ===================================================================
|
||||
-- requiressl and some other parameters are omitted because
|
||||
-- valid values for them depend on configure options
|
||||
ALTER SERVER testserver1 OPTIONS (
|
||||
use_remote_estimate 'false',
|
||||
updatable 'true',
|
||||
fdw_startup_cost '123.456',
|
||||
fdw_tuple_cost '0.123',
|
||||
service 'value',
|
||||
connect_timeout 'value',
|
||||
dbname 'value',
|
||||
host 'value',
|
||||
hostaddr 'value',
|
||||
port 'value',
|
||||
--client_encoding 'value',
|
||||
application_name 'value',
|
||||
--fallback_application_name 'value',
|
||||
keepalives 'value',
|
||||
keepalives_idle 'value',
|
||||
keepalives_interval 'value',
|
||||
-- requiressl 'value',
|
||||
sslcompression 'value',
|
||||
sslmode 'value',
|
||||
sslcert 'value',
|
||||
sslkey 'value',
|
||||
sslrootcert 'value',
|
||||
sslcrl 'value',
|
||||
--requirepeer 'value',
|
||||
krbsrvname 'value',
|
||||
gsslib 'value'
|
||||
--replication 'value'
|
||||
);
|
||||
ALTER USER MAPPING FOR public SERVER testserver1
|
||||
OPTIONS (DROP user, DROP password);
|
||||
ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1');
|
||||
ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
|
||||
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
|
||||
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
|
||||
\det+
|
||||
|
||||
-- Test that alteration of server options causes reconnection
|
||||
-- Remote's errors might be non-English, so hide them to ensure stable results
|
||||
\set VERBOSITY terse
|
||||
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work
|
||||
ALTER SERVER loopback OPTIONS (SET dbname 'no such database');
|
||||
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail
|
||||
DO $d$
|
||||
BEGIN
|
||||
EXECUTE $$ALTER SERVER loopback
|
||||
OPTIONS (SET dbname '$$||current_database()||$$')$$;
|
||||
END;
|
||||
$d$;
|
||||
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again
|
||||
|
||||
-- Test that alteration of user mapping options causes reconnection
|
||||
ALTER USER MAPPING FOR CURRENT_USER SERVER loopback
|
||||
OPTIONS (ADD user 'no such user');
|
||||
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail
|
||||
ALTER USER MAPPING FOR CURRENT_USER SERVER loopback
|
||||
OPTIONS (DROP user);
|
||||
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again
|
||||
\set VERBOSITY default
|
||||
|
||||
-- Now we should be able to run ANALYZE.
|
||||
-- To exercise multiple code paths, we use local stats on ft1
|
||||
-- and remote-estimate mode on ft2.
|
||||
ANALYZE ft1;
|
||||
ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true');
|
||||
|
||||
-- ===================================================================
|
||||
-- simple queries
|
||||
-- ===================================================================
|
||||
-- single table, with/without alias
|
||||
EXPLAIN (COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
|
||||
SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
|
||||
SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
|
||||
-- whole-row reference
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
|
||||
SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
|
||||
-- empty result
|
||||
SELECT * FROM ft1 WHERE false;
|
||||
-- with WHERE clause
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
|
||||
SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
|
||||
-- with FOR UPDATE/SHARE
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE;
|
||||
SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE;
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE;
|
||||
SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE;
|
||||
-- aggregate
|
||||
SELECT COUNT(*) FROM ft1 t1;
|
||||
-- join two tables
|
||||
SELECT t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
|
||||
-- subquery
|
||||
SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1;
|
||||
-- subquery+MAX
|
||||
SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1;
|
||||
-- used in CTE
|
||||
WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1;
|
||||
-- fixed values
|
||||
SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1;
|
||||
-- user-defined operator/function
|
||||
CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$
|
||||
BEGIN
|
||||
RETURN abs($1);
|
||||
END
|
||||
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||
CREATE OPERATOR === (
|
||||
LEFTARG = int,
|
||||
RIGHTARG = int,
|
||||
PROCEDURE = int4eq,
|
||||
COMMUTATOR = ===,
|
||||
NEGATOR = !==
|
||||
);
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2;
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
|
||||
|
||||
-- ===================================================================
|
||||
-- WHERE with remotely-executable conditions
|
||||
-- ===================================================================
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l)
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r)
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote
|
||||
-- parameterized remote path
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
|
||||
SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
|
||||
-- check both safe and unsafe join conditions
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT * FROM ft2 a, ft2 b
|
||||
WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7);
|
||||
SELECT * FROM ft2 a, ft2 b
|
||||
WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7);
|
||||
-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters
|
||||
SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5));
|
||||
SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5));
|
||||
|
||||
-- bug #15613: bad plan for foreign table scan with lateral reference
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT ref_0.c2, subq_1.*
|
||||
FROM
|
||||
"S 1"."T 1" AS ref_0,
|
||||
LATERAL (
|
||||
SELECT ref_0."C 1" c1, subq_0.*
|
||||
FROM (SELECT ref_0.c2, ref_1.c3
|
||||
FROM ft1 AS ref_1) AS subq_0
|
||||
RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
|
||||
) AS subq_1
|
||||
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
|
||||
ORDER BY ref_0."C 1";
|
||||
|
||||
SELECT ref_0.c2, subq_1.*
|
||||
FROM
|
||||
"S 1"."T 1" AS ref_0,
|
||||
LATERAL (
|
||||
SELECT ref_0."C 1" c1, subq_0.*
|
||||
FROM (SELECT ref_0.c2, ref_1.c3
|
||||
FROM ft1 AS ref_1) AS subq_0
|
||||
RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
|
||||
) AS subq_1
|
||||
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
|
||||
ORDER BY ref_0."C 1";
|
||||
|
||||
-- ===================================================================
|
||||
-- parameterized queries
|
||||
-- ===================================================================
|
||||
-- simple join
|
||||
PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2);
|
||||
EXECUTE st1(1, 1);
|
||||
EXECUTE st1(101, 101);
|
||||
-- subquery using stable function (can't be sent to remote)
|
||||
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
|
||||
EXECUTE st2(10, 20);
|
||||
EXECUTE st2(101, 121);
|
||||
-- subquery using immutable function (can be sent to remote)
|
||||
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
|
||||
EXECUTE st3(10, 20);
|
||||
EXECUTE st3(20, 30);
|
||||
-- custom plan should be chosen initially
|
||||
PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
-- once we try it enough times, should switch to generic plan
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
-- value of $1 should not be sent to remote
|
||||
PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXECUTE st5('foo', 1);
|
||||
|
||||
-- altering FDW options requires replanning
|
||||
PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
|
||||
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
|
||||
PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo');
|
||||
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
|
||||
ALTER TABLE "S 1"."T 1" RENAME TO "T 0";
|
||||
ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0');
|
||||
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
|
||||
EXECUTE st6;
|
||||
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
|
||||
ALTER TABLE "S 1"."T 0" RENAME TO "T 1";
|
||||
ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1');
|
||||
|
||||
-- cleanup
|
||||
DEALLOCATE st1;
|
||||
DEALLOCATE st2;
|
||||
DEALLOCATE st3;
|
||||
DEALLOCATE st4;
|
||||
DEALLOCATE st5;
|
||||
DEALLOCATE st6;
|
||||
DEALLOCATE st7;
|
||||
|
||||
-- System columns, except ctid, should not be sent to remote
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1;
|
||||
SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1;
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
|
||||
SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
|
||||
SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT ctid, * FROM ft1 t1 LIMIT 1;
|
||||
SELECT ctid, * FROM ft1 t1 LIMIT 1;
|
||||
|
||||
-- ===================================================================
|
||||
-- used in pl/pgsql function
|
||||
-- ===================================================================
|
||||
CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$
|
||||
DECLARE
|
||||
v_c1 int;
|
||||
BEGIN
|
||||
SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1;
|
||||
PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1;
|
||||
RETURN v_c1;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
SELECT f_test(100);
|
||||
DROP FUNCTION f_test(int);
|
||||
|
||||
-- ===================================================================
|
||||
-- conversion error
|
||||
-- ===================================================================
|
||||
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int;
|
||||
SELECT * FROM ft1 WHERE c1 = 1; -- ERROR
|
||||
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
|
||||
|
||||
-- ===================================================================
|
||||
-- subtransaction
|
||||
-- + local/remote error doesn't break cursor
|
||||
-- ===================================================================
|
||||
BEGIN;
|
||||
DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1;
|
||||
FETCH c;
|
||||
SAVEPOINT s;
|
||||
ERROR OUT; -- ERROR
|
||||
ROLLBACK TO s;
|
||||
FETCH c;
|
||||
SAVEPOINT s;
|
||||
SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR
|
||||
ROLLBACK TO s;
|
||||
FETCH c;
|
||||
SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
|
||||
COMMIT;
|
||||
|
||||
-- ===================================================================
|
||||
-- test handling of collations
|
||||
-- ===================================================================
|
||||
create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique);
|
||||
create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10))
|
||||
server loopback options (table_name 'loct3', use_remote_estimate 'true');
|
||||
|
||||
-- can be sent to remote
|
||||
explain (verbose, costs off) select * from ft3 where f1 = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f2 = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f3 = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 f, loct3 l
|
||||
where f.f3 = l.f3 and l.f1 = 'foo';
|
||||
-- can't be sent to remote
|
||||
explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C";
|
||||
explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C";
|
||||
explain (verbose, costs off) select * from ft3 f, loct3 l
|
||||
where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo';
|
||||
|
||||
-- ===================================================================
|
||||
-- test writable foreign table stuff
|
||||
-- ===================================================================
|
||||
EXPLAIN (verbose, costs off)
|
||||
INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
|
||||
INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
|
||||
INSERT INTO ft2 (c1,c2,c3)
|
||||
VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
|
||||
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
|
||||
UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
|
||||
EXPLAIN (verbose, costs off)
|
||||
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
|
||||
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
|
||||
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
|
||||
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
|
||||
EXPLAIN (verbose, costs off)
|
||||
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
|
||||
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
|
||||
EXPLAIN (verbose, costs off)
|
||||
DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
|
||||
DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
|
||||
SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
|
||||
EXPLAIN (verbose, costs off)
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
|
||||
EXPLAIN (verbose, costs off)
|
||||
UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
EXPLAIN (verbose, costs off)
|
||||
DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
|
||||
-- Test that trigger on remote table works as expected
|
||||
CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW.c3 = NEW.c3 || '_trig_update';
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE
|
||||
ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG();
|
||||
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *;
|
||||
INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *;
|
||||
UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *;
|
||||
|
||||
-- Test errors thrown on remote side during update
|
||||
ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);
|
||||
|
||||
INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key
|
||||
INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
|
||||
UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
|
||||
|
||||
-- Test savepoint/rollback behavior
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
|
||||
begin;
|
||||
update ft2 set c2 = 42 where c2 = 0;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
savepoint s1;
|
||||
update ft2 set c2 = 44 where c2 = 4;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
release savepoint s1;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
savepoint s2;
|
||||
update ft2 set c2 = 46 where c2 = 6;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
rollback to savepoint s2;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
release savepoint s2;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
savepoint s3;
|
||||
update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side
|
||||
rollback to savepoint s3;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
release savepoint s3;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
-- none of the above is committed yet remotely
|
||||
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
|
||||
commit;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
|
||||
|
||||
-- ===================================================================
|
||||
-- test serial columns (ie, sequence-based defaults)
|
||||
-- ===================================================================
|
||||
create table loc1 (f1 serial, f2 text);
|
||||
create foreign table rem1 (f1 serial, f2 text)
|
||||
server loopback options(table_name 'loc1');
|
||||
select pg_catalog.setval('rem1_f1_seq', 10, false);
|
||||
insert into loc1(f2) values('hi');
|
||||
insert into rem1(f2) values('hi remote');
|
||||
insert into loc1(f2) values('bye');
|
||||
insert into rem1(f2) values('bye remote');
|
||||
select * from loc1;
|
||||
select * from rem1;
|
||||
|
||||
-- ===================================================================
|
||||
-- test local triggers
|
||||
-- ===================================================================
|
||||
|
||||
-- Trigger functions "borrowed" from triggers regress test.
|
||||
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %',
|
||||
TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
|
||||
RETURN NULL;
|
||||
END;$$;
|
||||
|
||||
CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1
|
||||
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
|
||||
CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1
|
||||
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
|
||||
|
||||
CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger
|
||||
LANGUAGE plpgsql AS $$
|
||||
|
||||
declare
|
||||
oldnew text[];
|
||||
relid text;
|
||||
argstr text;
|
||||
begin
|
||||
|
||||
relid := TG_relid::regclass;
|
||||
argstr := '';
|
||||
for i in 0 .. TG_nargs - 1 loop
|
||||
if i > 0 then
|
||||
argstr := argstr || ', ';
|
||||
end if;
|
||||
argstr := argstr || TG_argv[i];
|
||||
end loop;
|
||||
|
||||
RAISE NOTICE '%(%) % % % ON %',
|
||||
tg_name, argstr, TG_when, TG_level, TG_OP, relid;
|
||||
oldnew := '{}'::text[];
|
||||
if TG_OP != 'INSERT' then
|
||||
oldnew := array_append(oldnew, format('OLD: %s', OLD));
|
||||
end if;
|
||||
|
||||
if TG_OP != 'DELETE' then
|
||||
oldnew := array_append(oldnew, format('NEW: %s', NEW));
|
||||
end if;
|
||||
|
||||
RAISE NOTICE '%', array_to_string(oldnew, ',');
|
||||
|
||||
if TG_OP = 'DELETE' then
|
||||
return OLD;
|
||||
else
|
||||
return NEW;
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Test basic functionality
|
||||
CREATE TRIGGER trig_row_before
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
CREATE TRIGGER trig_row_after
|
||||
AFTER INSERT OR UPDATE OR DELETE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
delete from rem1;
|
||||
insert into rem1 values(1,'insert');
|
||||
update rem1 set f2 = 'update' where f1 = 1;
|
||||
update rem1 set f2 = f2 || f2;
|
||||
|
||||
|
||||
-- cleanup
|
||||
DROP TRIGGER trig_row_before ON rem1;
|
||||
DROP TRIGGER trig_row_after ON rem1;
|
||||
DROP TRIGGER trig_stmt_before ON rem1;
|
||||
DROP TRIGGER trig_stmt_after ON rem1;
|
||||
|
||||
DELETE from rem1;
|
||||
|
||||
|
||||
-- Test WHEN conditions
|
||||
|
||||
CREATE TRIGGER trig_row_before_insupd
|
||||
BEFORE INSERT OR UPDATE ON rem1
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.f2 like '%update%')
|
||||
EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
CREATE TRIGGER trig_row_after_insupd
|
||||
AFTER INSERT OR UPDATE ON rem1
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.f2 like '%update%')
|
||||
EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
-- Insert or update not matching: nothing happens
|
||||
INSERT INTO rem1 values(1, 'insert');
|
||||
UPDATE rem1 set f2 = 'test';
|
||||
|
||||
-- Insert or update matching: triggers are fired
|
||||
INSERT INTO rem1 values(2, 'update');
|
||||
UPDATE rem1 set f2 = 'update update' where f1 = '2';
|
||||
|
||||
CREATE TRIGGER trig_row_before_delete
|
||||
BEFORE DELETE ON rem1
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.f2 like '%update%')
|
||||
EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
CREATE TRIGGER trig_row_after_delete
|
||||
AFTER DELETE ON rem1
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.f2 like '%update%')
|
||||
EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
-- Trigger is fired for f1=2, not for f1=1
|
||||
DELETE FROM rem1;
|
||||
|
||||
-- cleanup
|
||||
DROP TRIGGER trig_row_before_insupd ON rem1;
|
||||
DROP TRIGGER trig_row_after_insupd ON rem1;
|
||||
DROP TRIGGER trig_row_before_delete ON rem1;
|
||||
DROP TRIGGER trig_row_after_delete ON rem1;
|
||||
|
||||
|
||||
-- Test various RETURN statements in BEFORE triggers.
|
||||
|
||||
CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.f2 := NEW.f2 || ' triggered !';
|
||||
RETURN NEW;
|
||||
END
|
||||
$$ language plpgsql;
|
||||
|
||||
CREATE TRIGGER trig_row_before_insupd
|
||||
BEFORE INSERT OR UPDATE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
|
||||
|
||||
-- The new values should have 'triggered' appended
|
||||
INSERT INTO rem1 values(1, 'insert');
|
||||
SELECT * from loc1;
|
||||
INSERT INTO rem1 values(2, 'insert') RETURNING f2;
|
||||
SELECT * from loc1;
|
||||
UPDATE rem1 set f2 = '';
|
||||
SELECT * from loc1;
|
||||
UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
|
||||
SELECT * from loc1;
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
UPDATE rem1 set f1 = 10; -- all columns should be transmitted
|
||||
UPDATE rem1 set f1 = 10;
|
||||
SELECT * from loc1;
|
||||
|
||||
DELETE FROM rem1;
|
||||
|
||||
-- Add a second trigger, to check that the changes are propagated correctly
|
||||
-- from trigger to trigger
|
||||
CREATE TRIGGER trig_row_before_insupd2
|
||||
BEFORE INSERT OR UPDATE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
|
||||
|
||||
INSERT INTO rem1 values(1, 'insert');
|
||||
SELECT * from loc1;
|
||||
INSERT INTO rem1 values(2, 'insert') RETURNING f2;
|
||||
SELECT * from loc1;
|
||||
UPDATE rem1 set f2 = '';
|
||||
SELECT * from loc1;
|
||||
UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
|
||||
SELECT * from loc1;
|
||||
|
||||
DROP TRIGGER trig_row_before_insupd ON rem1;
|
||||
DROP TRIGGER trig_row_before_insupd2 ON rem1;
|
||||
|
||||
DELETE from rem1;
|
||||
|
||||
INSERT INTO rem1 VALUES (1, 'test');
|
||||
|
||||
-- Test with a trigger returning NULL
|
||||
CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
RETURN NULL;
|
||||
END
|
||||
$$ language plpgsql;
|
||||
|
||||
CREATE TRIGGER trig_null
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trig_null();
|
||||
|
||||
-- Nothing should have changed.
|
||||
INSERT INTO rem1 VALUES (2, 'test2');
|
||||
|
||||
SELECT * from loc1;
|
||||
|
||||
UPDATE rem1 SET f2 = 'test2';
|
||||
|
||||
SELECT * from loc1;
|
||||
|
||||
DELETE from rem1;
|
||||
|
||||
SELECT * from loc1;
|
||||
|
||||
DROP TRIGGER trig_null ON rem1;
|
||||
DELETE from rem1;
|
||||
|
||||
-- Test a combination of local and remote triggers
|
||||
CREATE TRIGGER trig_row_before
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
CREATE TRIGGER trig_row_after
|
||||
AFTER INSERT OR UPDATE OR DELETE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
|
||||
|
||||
INSERT INTO rem1(f2) VALUES ('test');
|
||||
UPDATE rem1 SET f2 = 'testo';
|
||||
|
||||
-- Test returning a system attribute
|
||||
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
|
||||
|
|
@ -1527,7 +1527,7 @@ Datum fmgr_c_validator(PG_FUNCTION_ARGS)
|
|||
probin = TextDatumGetCString(tmp);
|
||||
if (strcmp(probin, "$libdir/plpgsql") && strcmp(probin, "$libdir/dist_fdw") && strcmp(probin, "$libdir/file_fdw") &&
|
||||
strcmp(probin, "$libdir/mot_fdw") && strcmp(probin, "$libdir/log_fdw") &&
|
||||
strcmp(probin, "$libdir/hdfs_fdw") && strcmp(probin, "$libdir/postgres_fdw")) {
|
||||
strcmp(probin, "$libdir/hdfs_fdw")) {
|
||||
(void)load_external_function(probin, prosrc, true, true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -511,8 +511,7 @@ static void fmgr_info_C_lang(Oid functionId, FmgrInfo* finfo, HeapTuple procedur
|
|||
/* Look up the function itself */
|
||||
if (strcmp(pro_bin_string, "$libdir/plpgsql") && strcmp(pro_bin_string, "$libdir/dist_fdw") &&
|
||||
strcmp(pro_bin_string, "$libdir/file_fdw") && strcmp(pro_bin_string, "$libdir/mot_fdw") &&
|
||||
strcmp(pro_bin_string, "$libdir/log_fdw") && strcmp(pro_bin_string, "$libdir/hdfs_fdw") &&
|
||||
strcmp(pro_bin_string, "$libdir/postgres_fdw")) {
|
||||
strcmp(pro_bin_string, "$libdir/log_fdw") && strcmp(pro_bin_string, "$libdir/hdfs_fdw")) {
|
||||
funInfo = load_external_function(pro_bin_string, pro_src_string, true, false);
|
||||
user_fn = funInfo.user_fn;
|
||||
inforec = funInfo.inforec;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ override CXXFLAGS += $(PTHREAD_CFLAGS)
|
|||
SUBDIRS = ../common/backend bootstrap cbb optimizer process runtime security storage \
|
||||
$(top_builddir)/src/common/timezone $(top_builddir)/src/common/interfaces/libpq \
|
||||
$(top_builddir)/contrib/file_fdw $(top_builddir)/contrib/hdfs_fdw \
|
||||
$(top_builddir)/contrib/log_fdw $(top_builddir)/contrib/test_decoding $(top_builddir)/contrib/mppdb_decoding
|
||||
$(top_builddir)/contrib/log_fdw $(top_builddir)/contrib/test_decoding $(top_builddir)/contrib/mppdb_decoding \
|
||||
$(top_builddir)/contrib/postgres_fdw
|
||||
|
||||
ifeq ($(enable_mysql_fdw), yes)
|
||||
SUBDIRS += $(top_builddir)/contrib/mysql_fdw
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ bool IsSpecifiedFDWFromRelid(Oid relId, const char* SepcifiedType)
|
|||
*/
|
||||
bool CheckSupportedFDWType(Oid relId)
|
||||
{
|
||||
static const char* supportFDWType[] = {MOT_FDW, MYSQL_FDW, ORACLE_FDW};
|
||||
static const char* supportFDWType[] = {MOT_FDW, MYSQL_FDW, ORACLE_FDW, POSTGRES_FDW};
|
||||
int size = sizeof(supportFDWType) / sizeof(supportFDWType[0]);
|
||||
bool support = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -896,7 +896,8 @@ HeapTuple* get_total_rows(Relation onerel, VacuumStmt* vacstmt, BlockNumber relp
|
|||
onerel, vacstmt, elevel, rows, target_rows, totalrows, totaldeadrows, vacattrstats, attr_cnt);
|
||||
} else if (isForeignTable ||
|
||||
(onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE &&
|
||||
(isMOTFromTblOid(RelationGetRelid(onerel)) || isOracleFDWFromTblOid(RelationGetRelid(onerel))))) {
|
||||
(isMOTFromTblOid(RelationGetRelid(onerel)) || isOracleFDWFromTblOid(RelationGetRelid(onerel)) ||
|
||||
isPostgresFDWFromTblOid(RelationGetRelid(onerel))))) {
|
||||
/*
|
||||
* @hdfs processing foreign table sampling operation
|
||||
* get foreign table FDW routine
|
||||
|
|
|
|||
|
|
@ -4169,10 +4169,8 @@ static uint64 CopyFrom(CopyState cstate)
|
|||
/* Handle queued AFTER triggers */
|
||||
AfterTriggerEndQuery(estate);
|
||||
|
||||
/* To free the statement allocated in ExecForeignInsert */
|
||||
if (resultRelInfo->ri_FdwRoutine && resultRelInfo->ri_FdwRoutine->GetFdwType &&
|
||||
(resultRelInfo->ri_FdwRoutine->GetFdwType() == MYSQL_ORC ||
|
||||
resultRelInfo->ri_FdwRoutine->GetFdwType() == ORACLE_ORC)) {
|
||||
/* To free the statement allocated in ExecForeignInsert, MOT doesn't need this */
|
||||
if (isForeignTbl && !isMOTFromTblOid(RelationGetRelid(cstate->rel))) {
|
||||
resultRelInfo->ri_FdwRoutine->EndForeignModify(estate, resultRelInfo);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1858,7 +1858,8 @@ Oid DefineRelation(CreateStmt* stmt, char relkind, Oid ownerId)
|
|||
RawColumnDefault* rawEnt = NULL;
|
||||
if (relkind == RELKIND_FOREIGN_TABLE) {
|
||||
if (!(IsA(stmt, CreateForeignTableStmt) &&
|
||||
isMOTTableFromSrvName(((CreateForeignTableStmt*)stmt)->servername)))
|
||||
(isMOTTableFromSrvName(((CreateForeignTableStmt*)stmt)->servername) ||
|
||||
isPostgresFDWFromSrvName(((CreateForeignTableStmt*)stmt)->servername))))
|
||||
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("default values on foreign tables are not supported")));
|
||||
}
|
||||
|
|
@ -7735,7 +7736,7 @@ static void ATExecAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel,
|
|||
RawColumnDefault* rawEnt = NULL;
|
||||
|
||||
if (relkind == RELKIND_FOREIGN_TABLE) {
|
||||
if (!isMOTFromTblOid(RelationGetRelid(rel)))
|
||||
if (!isMOTFromTblOid(RelationGetRelid(rel)) && !isPostgresFDWFromTblOid(RelationGetRelid(rel)))
|
||||
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("default values on foreign tables are not supported")));
|
||||
}
|
||||
|
|
@ -14479,6 +14480,12 @@ static void ATExecGenericOptions(Relation rel, List* options)
|
|||
simple_heap_update(ftrel, &tuple->t_self, tuple);
|
||||
CatalogUpdateIndexes(ftrel, tuple);
|
||||
|
||||
/*
|
||||
* Invalidate relcache so that all sessions will refresh any cached plans
|
||||
* that might depend on the old options.
|
||||
*/
|
||||
CacheInvalidateRelcache(rel);
|
||||
|
||||
heap_close(ftrel, RowExclusiveLock);
|
||||
|
||||
heap_freetuple_ext(tuple);
|
||||
|
|
|
|||
|
|
@ -2171,9 +2171,10 @@ static long do_portal_run_fetch(Portal portal, FetchDirection fdirection, long c
|
|||
*/
|
||||
static void do_portal_rewind(Portal portal)
|
||||
{
|
||||
#ifdef ENABLE_MULTIPLE_NODES
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmodule(MOD_EXECUTOR), errmsg("Cursor rewind are not supported.")));
|
||||
|
||||
#endif
|
||||
if (portal->holdStore) {
|
||||
MemoryContext old_context;
|
||||
|
||||
|
|
|
|||
|
|
@ -8519,8 +8519,9 @@ bool DropExtensionIsSupported(const char* query_string)
|
|||
{
|
||||
char* lower_string = lowerstr(query_string);
|
||||
|
||||
if (strstr(lower_string, "drop") && (strstr(lower_string, "postgis") || strstr(lower_string, "packages") ||
|
||||
strstr(lower_string, "mysql_fdw") || strstr(lower_string, "oracle_fdw"))) {
|
||||
if (strstr(lower_string, "drop") && (strstr(lower_string, "postgis") || strstr(lower_string, "packages") ||
|
||||
strstr(lower_string, "mysql_fdw") || strstr(lower_string, "oracle_fdw") ||
|
||||
strstr(lower_string, "postgres_fdw"))) {
|
||||
pfree_ext(lower_string);
|
||||
return true;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -289,12 +289,18 @@ bool isWriteOnlyFt(Oid relid);
|
|||
#define isMOTTableFromSrvName(srvName) \
|
||||
(IsSpecifiedFDW(srvName, MOT_FDW))
|
||||
|
||||
#define isPostgresFDWFromSrvName(srvName) \
|
||||
(IsSpecifiedFDW(srvName, POSTGRES_FDW))
|
||||
|
||||
#define isMysqlFDWFromTblOid(relId) \
|
||||
(IsSpecifiedFDWFromRelid(relId, MYSQL_FDW))
|
||||
|
||||
#define isOracleFDWFromTblOid(relId) \
|
||||
(IsSpecifiedFDWFromRelid(relId, ORACLE_FDW))
|
||||
|
||||
#define isPostgresFDWFromTblOid(relId) \
|
||||
(IsSpecifiedFDWFromRelid(relId, POSTGRES_FDW))
|
||||
|
||||
#define IS_OBS_CSV_TXT_FOREIGN_TABLE(relId) \
|
||||
(IsSpecifiedFDWFromRelid(relId, DIST_FDW) && (is_obs_protocol(HdfsGetOptionValue(relId, optLocation))))
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@
|
|||
#define ORACLE_FDW "oracle_fdw"
|
||||
#endif
|
||||
|
||||
#ifndef POSTGRES_FDW
|
||||
#define POSTGRES_FDW "postgres_fdw"
|
||||
#endif
|
||||
|
||||
#ifndef MOT_FDW
|
||||
#define MOT_FDW "mot_fdw"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,780 @@
|
|||
-- ===================================================================
|
||||
-- create FDW objects
|
||||
-- ===================================================================
|
||||
|
||||
CREATE EXTENSION postgres_fdw;
|
||||
|
||||
CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw;
|
||||
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
|
||||
OPTIONS (dbname 'regression', port '@portstring@');
|
||||
|
||||
CREATE USER MAPPING FOR public SERVER testserver1
|
||||
OPTIONS (user 'value', password 'value');
|
||||
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
|
||||
|
||||
-- ===================================================================
|
||||
-- create objects used through FDW loopback server
|
||||
-- ===================================================================
|
||||
CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz');
|
||||
CREATE SCHEMA "S 1";
|
||||
CREATE TABLE "S 1"."T 1" (
|
||||
"C 1" int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
c3 text,
|
||||
c4 timestamptz,
|
||||
c5 timestamp,
|
||||
c6 varchar(10),
|
||||
c7 char(10),
|
||||
c8 user_enum,
|
||||
CONSTRAINT t1_pkey PRIMARY KEY ("C 1")
|
||||
);
|
||||
CREATE TABLE "S 1"."T 2" (
|
||||
c1 int NOT NULL,
|
||||
c2 text,
|
||||
CONSTRAINT t2_pkey PRIMARY KEY (c1)
|
||||
);
|
||||
|
||||
INSERT INTO "S 1"."T 1"
|
||||
SELECT id,
|
||||
id % 10,
|
||||
to_char(id, 'FM00000'),
|
||||
'1970-01-01'::timestamptz + ((id % 100) || ' days')::interval,
|
||||
'1970-01-01'::timestamp + ((id % 100) || ' days')::interval,
|
||||
id % 10,
|
||||
id % 10,
|
||||
'foo'::user_enum
|
||||
FROM generate_series(1, 1000) id;
|
||||
INSERT INTO "S 1"."T 2"
|
||||
SELECT id,
|
||||
'AAA' || to_char(id, 'FM000')
|
||||
FROM generate_series(1, 100) id;
|
||||
|
||||
ANALYZE "S 1"."T 1";
|
||||
ANALYZE "S 1"."T 2";
|
||||
|
||||
-- ===================================================================
|
||||
-- create local tables to check whether the grammer is support
|
||||
-- ===================================================================
|
||||
CREATE TABLE local_ft1 (
|
||||
c1 int NOT NULL,
|
||||
"C 1" int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
c3 text,
|
||||
c4 timestamptz,
|
||||
c5 timestamp,
|
||||
c6 varchar(10),
|
||||
c7 char(10) default 'ft1',
|
||||
c8 user_enum
|
||||
);
|
||||
|
||||
CREATE TABLE local_ft2 (
|
||||
c1 int NOT NULL,
|
||||
"C 1" int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
c3 text,
|
||||
c4 timestamptz,
|
||||
c5 timestamp,
|
||||
c6 varchar(10),
|
||||
c7 char(10) default 'ft2',
|
||||
c8 user_enum
|
||||
);
|
||||
-- ===================================================================
|
||||
-- create foreign tables
|
||||
-- ===================================================================
|
||||
CREATE FOREIGN TABLE ft1 (
|
||||
c0 int,
|
||||
c1 int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
c3 text,
|
||||
c4 timestamptz,
|
||||
c5 timestamp,
|
||||
c6 varchar(10),
|
||||
c7 char(10) default 'ft1',
|
||||
c8 user_enum
|
||||
) SERVER loopback;
|
||||
ALTER FOREIGN TABLE ft1 DROP COLUMN c0;
|
||||
|
||||
CREATE FOREIGN TABLE ft2 (
|
||||
c1 int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
cx int,
|
||||
c3 text,
|
||||
c4 timestamptz,
|
||||
c5 timestamp,
|
||||
c6 varchar(10),
|
||||
c7 char(10) default 'ft2',
|
||||
c8 user_enum
|
||||
) SERVER loopback;
|
||||
ALTER FOREIGN TABLE ft2 DROP COLUMN cx;
|
||||
|
||||
-- ===================================================================
|
||||
-- tests for validator
|
||||
-- ===================================================================
|
||||
-- requiressl and some other parameters are omitted because
|
||||
-- valid values for them depend on configure options
|
||||
ALTER SERVER testserver1 OPTIONS (
|
||||
use_remote_estimate 'false',
|
||||
updatable 'true',
|
||||
fdw_startup_cost '123.456',
|
||||
fdw_tuple_cost '0.123',
|
||||
service 'value',
|
||||
connect_timeout 'value',
|
||||
dbname 'value',
|
||||
host 'value',
|
||||
hostaddr 'value',
|
||||
port 'value',
|
||||
--client_encoding 'value',
|
||||
application_name 'value',
|
||||
--fallback_application_name 'value',
|
||||
keepalives 'value',
|
||||
keepalives_idle 'value',
|
||||
keepalives_interval 'value',
|
||||
-- requiressl 'value',
|
||||
sslcompression 'value',
|
||||
sslmode 'value',
|
||||
sslcert 'value',
|
||||
sslkey 'value',
|
||||
sslrootcert 'value',
|
||||
sslcrl 'value',
|
||||
--requirepeer 'value',
|
||||
krbsrvname 'value'
|
||||
--gsslib 'value'
|
||||
--replication 'value'
|
||||
);
|
||||
ALTER USER MAPPING FOR public SERVER testserver1
|
||||
OPTIONS (DROP user, DROP password);
|
||||
ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1');
|
||||
ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
|
||||
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
|
||||
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
|
||||
\det+
|
||||
|
||||
-- Test that alteration of server options causes reconnection
|
||||
-- Remote's errors might be non-English, so hide them to ensure stable results
|
||||
\set VERBOSITY terse
|
||||
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work
|
||||
ALTER SERVER loopback OPTIONS (SET dbname 'no such database');
|
||||
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail
|
||||
DO $d$
|
||||
BEGIN
|
||||
EXECUTE $$ALTER SERVER loopback
|
||||
OPTIONS (SET dbname '$$||current_database()||$$')$$;
|
||||
END;
|
||||
$d$;
|
||||
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again
|
||||
|
||||
-- Test that alteration of user mapping options causes reconnection
|
||||
ALTER USER MAPPING FOR CURRENT_USER SERVER loopback
|
||||
OPTIONS (ADD user 'no such user');
|
||||
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail
|
||||
ALTER USER MAPPING FOR CURRENT_USER SERVER loopback
|
||||
OPTIONS (DROP user);
|
||||
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again
|
||||
\set VERBOSITY default
|
||||
|
||||
-- Now we should be able to run ANALYZE.
|
||||
-- To exercise multiple code paths, we use local stats on ft1
|
||||
-- and remote-estimate mode on ft2.
|
||||
ANALYZE ft1;
|
||||
ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true');
|
||||
|
||||
-- ===================================================================
|
||||
-- simple queries
|
||||
-- ===================================================================
|
||||
-- single table, with/without alias
|
||||
EXPLAIN (COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
|
||||
SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
|
||||
SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
|
||||
-- whole-row reference
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
|
||||
SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
|
||||
-- empty result
|
||||
SELECT * FROM ft1 WHERE false;
|
||||
-- with WHERE clause
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
|
||||
SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
|
||||
-- with FOR UPDATE/SHARE
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE;
|
||||
SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE;
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE;
|
||||
SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE;
|
||||
-- aggregate
|
||||
SELECT COUNT(*) FROM ft1 t1;
|
||||
-- join two tables
|
||||
SELECT t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
|
||||
-- subquery
|
||||
SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1;
|
||||
-- subquery+MAX
|
||||
SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1;
|
||||
-- used in CTE
|
||||
WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1;
|
||||
-- fixed values
|
||||
SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1;
|
||||
-- user-defined operator/function
|
||||
CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$
|
||||
BEGIN
|
||||
RETURN abs($1);
|
||||
END
|
||||
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||
CREATE OPERATOR === (
|
||||
LEFTARG = int,
|
||||
RIGHTARG = int,
|
||||
PROCEDURE = int4eq,
|
||||
COMMUTATOR = ===,
|
||||
NEGATOR = !==
|
||||
);
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2;
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
|
||||
|
||||
-- ===================================================================
|
||||
-- WHERE with remotely-executable conditions
|
||||
-- ===================================================================
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l)
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r)
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars
|
||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote
|
||||
-- parameterized remote path
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
|
||||
SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
|
||||
-- check both safe and unsafe join conditions
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT * FROM ft2 a, ft2 b
|
||||
WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7);
|
||||
SELECT * FROM ft2 a, ft2 b
|
||||
WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7);
|
||||
-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters
|
||||
SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5));
|
||||
SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5));
|
||||
|
||||
-- bug #15613: bad plan for foreign table scan with lateral reference
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT ref_0.c2, subq_1.*
|
||||
FROM
|
||||
"S 1"."T 1" AS ref_0,
|
||||
LATERAL (
|
||||
SELECT ref_0."C 1" c1, subq_0.*
|
||||
FROM (SELECT ref_0.c2, ref_1.c3
|
||||
FROM ft1 AS ref_1) AS subq_0
|
||||
RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
|
||||
) AS subq_1
|
||||
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
|
||||
ORDER BY ref_0."C 1";
|
||||
|
||||
-- use local table to check whether this sql supported
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT ref_0.c2, subq_1.*
|
||||
FROM
|
||||
"S 1"."T 1" AS ref_0,
|
||||
LATERAL (
|
||||
SELECT ref_0."C 1" c1, subq_0.*
|
||||
FROM (SELECT ref_0.c2, ref_1.c3
|
||||
FROM local_ft1 AS ref_1) AS subq_0
|
||||
RIGHT JOIN local_ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
|
||||
) AS subq_1
|
||||
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
|
||||
ORDER BY ref_0."C 1";
|
||||
|
||||
SELECT ref_0.c2, subq_1.*
|
||||
FROM
|
||||
"S 1"."T 1" AS ref_0,
|
||||
LATERAL (
|
||||
SELECT ref_0."C 1" c1, subq_0.*
|
||||
FROM (SELECT ref_0.c2, ref_1.c3
|
||||
FROM ft1 AS ref_1) AS subq_0
|
||||
RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
|
||||
) AS subq_1
|
||||
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
|
||||
ORDER BY ref_0."C 1";
|
||||
|
||||
-- use local table to check whether this sql supported
|
||||
SELECT ref_0.c2, subq_1.*
|
||||
FROM
|
||||
"S 1"."T 1" AS ref_0,
|
||||
LATERAL (
|
||||
SELECT ref_0."C 1" c1, subq_0.*
|
||||
FROM (SELECT ref_0.c2, ref_1.c3
|
||||
FROM local_ft1 AS ref_1) AS subq_0
|
||||
RIGHT JOIN local_ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
|
||||
) AS subq_1
|
||||
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
|
||||
ORDER BY ref_0."C 1";
|
||||
|
||||
-- ===================================================================
|
||||
-- parameterized queries
|
||||
-- ===================================================================
|
||||
-- simple join
|
||||
PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2);
|
||||
EXECUTE st1(1, 1);
|
||||
EXECUTE st1(101, 101);
|
||||
-- subquery using stable function (can't be sent to remote)
|
||||
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND (c4) = '1970-01-17'::date) ORDER BY c1;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
|
||||
EXECUTE st2(10, 20);
|
||||
EXECUTE st2(101, 121);
|
||||
-- subquery using immutable function (can be sent to remote)
|
||||
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND (c5) = '1970-01-17'::date) ORDER BY c1;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
|
||||
EXECUTE st3(10, 20);
|
||||
EXECUTE st3(20, 30);
|
||||
-- custom plan should be chosen initially
|
||||
PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
-- once we try it enough times, should switch to generic plan
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
|
||||
-- value of $1 should not be sent to remote
|
||||
PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
||||
EXECUTE st5('foo', 1);
|
||||
|
||||
-- altering FDW options requires replanning
|
||||
PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
|
||||
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
|
||||
PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo');
|
||||
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
|
||||
ALTER TABLE "S 1"."T 1" RENAME TO "T 0";
|
||||
ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0');
|
||||
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
|
||||
EXECUTE st6;
|
||||
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
|
||||
ALTER TABLE "S 1"."T 0" RENAME TO "T 1";
|
||||
ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1');
|
||||
|
||||
-- cleanup
|
||||
DEALLOCATE st1;
|
||||
DEALLOCATE st2;
|
||||
DEALLOCATE st3;
|
||||
DEALLOCATE st4;
|
||||
DEALLOCATE st5;
|
||||
DEALLOCATE st6;
|
||||
DEALLOCATE st7;
|
||||
|
||||
-- System columns, except ctid, should not be sent to remote
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1;
|
||||
SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1;
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
|
||||
SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
|
||||
SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
|
||||
EXPLAIN (VERBOSE, COSTS false)
|
||||
SELECT ctid, * FROM ft1 t1 LIMIT 1;
|
||||
SELECT ctid, * FROM ft1 t1 LIMIT 1;
|
||||
|
||||
-- ===================================================================
|
||||
-- used in pl/pgsql function
|
||||
-- ===================================================================
|
||||
CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$
|
||||
DECLARE
|
||||
v_c1 int;
|
||||
BEGIN
|
||||
SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1;
|
||||
PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1;
|
||||
RETURN v_c1;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
SELECT f_test(100);
|
||||
DROP FUNCTION f_test(int);
|
||||
|
||||
-- ===================================================================
|
||||
-- conversion error
|
||||
-- ===================================================================
|
||||
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int;
|
||||
SELECT * FROM ft1 WHERE c1 = 1; -- ERROR
|
||||
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
|
||||
|
||||
-- ===================================================================
|
||||
-- subtransaction
|
||||
-- + local/remote error doesn't break cursor
|
||||
-- ===================================================================
|
||||
BEGIN;
|
||||
DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1;
|
||||
FETCH c;
|
||||
SAVEPOINT s;
|
||||
ERROR OUT; -- ERROR
|
||||
ROLLBACK TO s;
|
||||
FETCH c;
|
||||
SAVEPOINT s;
|
||||
SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR
|
||||
ROLLBACK TO s;
|
||||
FETCH c;
|
||||
SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
|
||||
COMMIT;
|
||||
|
||||
-- ===================================================================
|
||||
-- test handling of collations
|
||||
-- ===================================================================
|
||||
create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique);
|
||||
create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10))
|
||||
server loopback options (table_name 'loct3', use_remote_estimate 'true');
|
||||
|
||||
-- can be sent to remote
|
||||
explain (verbose, costs off) select * from ft3 where f1 = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f2 = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f3 = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 f, loct3 l
|
||||
where f.f3 = l.f3 and l.f1 = 'foo';
|
||||
-- can't be sent to remote
|
||||
explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C";
|
||||
explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C";
|
||||
explain (verbose, costs off) select * from ft3 f, loct3 l
|
||||
where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo';
|
||||
|
||||
-- ===================================================================
|
||||
-- test writable foreign table stuff
|
||||
-- ===================================================================
|
||||
EXPLAIN (verbose, costs off)
|
||||
INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
|
||||
INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
|
||||
INSERT INTO ft2 (c1,c2,c3)
|
||||
VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
|
||||
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
|
||||
UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
|
||||
EXPLAIN (verbose, costs off)
|
||||
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
|
||||
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
|
||||
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
|
||||
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
|
||||
EXPLAIN (verbose, costs off)
|
||||
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
|
||||
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
|
||||
EXPLAIN (verbose, costs off)
|
||||
DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
|
||||
DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
|
||||
SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
|
||||
EXPLAIN (verbose, costs off)
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
|
||||
EXPLAIN (verbose, costs off)
|
||||
UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
EXPLAIN (verbose, costs off)
|
||||
DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
|
||||
-- Test that trigger on remote table works as expected
|
||||
CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW.c3 = NEW.c3 || '_trig_update';
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE
|
||||
ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG();
|
||||
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *;
|
||||
INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *;
|
||||
UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *;
|
||||
|
||||
-- Test errors thrown on remote side during update
|
||||
ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);
|
||||
|
||||
INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key
|
||||
INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
|
||||
UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
|
||||
|
||||
-- Test savepoint/rollback behavior
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
|
||||
begin;
|
||||
update ft2 set c2 = 42 where c2 = 0;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
savepoint s1;
|
||||
update ft2 set c2 = 44 where c2 = 4;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
release savepoint s1;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
savepoint s2;
|
||||
update ft2 set c2 = 46 where c2 = 6;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
rollback to savepoint s2;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
release savepoint s2;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
savepoint s3;
|
||||
update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side
|
||||
rollback to savepoint s3;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
release savepoint s3;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
-- none of the above is committed yet remotely
|
||||
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
|
||||
commit;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
|
||||
|
||||
-- ===================================================================
|
||||
-- test copy
|
||||
-- ===================================================================
|
||||
select count(*) from ft2;
|
||||
select * from ft2 order by c1 limit 10;
|
||||
\! rm -f ./foreigntable_ft2.data
|
||||
\COPY (select * from ft2) to './foreigntable_ft2.data';
|
||||
delete from ft2;
|
||||
select * from ft2 order by c1 limit 10;
|
||||
\COPY ft2 from './foreigntable_ft2.data';
|
||||
select * from ft2 order by c1 limit 10;
|
||||
select count(*) from ft2;
|
||||
\! rm -f ./foreigntable_ft2.data
|
||||
|
||||
-- ===================================================================
|
||||
-- test serial columns (ie, sequence-based defaults)
|
||||
-- ===================================================================
|
||||
create table loc1 (f1 serial, f2 text);
|
||||
create foreign table rem1 (f1 serial, f2 text)
|
||||
server loopback options(table_name 'loc1');
|
||||
select pg_catalog.setval('rem1_f1_seq', 10, false);
|
||||
insert into loc1(f2) values('hi');
|
||||
insert into rem1(f2) values('hi remote');
|
||||
insert into loc1(f2) values('bye');
|
||||
insert into rem1(f2) values('bye remote');
|
||||
select * from loc1;
|
||||
select * from rem1;
|
||||
|
||||
-- ===================================================================
|
||||
-- test local triggers
|
||||
-- ===================================================================
|
||||
|
||||
-- Trigger functions "borrowed" from triggers regress test.
|
||||
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %',
|
||||
TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
|
||||
RETURN NULL;
|
||||
END;$$;
|
||||
|
||||
CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1
|
||||
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
|
||||
CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1
|
||||
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
|
||||
|
||||
CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger
|
||||
LANGUAGE plpgsql AS $$
|
||||
|
||||
declare
|
||||
oldnew text[];
|
||||
relid text;
|
||||
argstr text;
|
||||
begin
|
||||
|
||||
relid := TG_relid::regclass;
|
||||
argstr := '';
|
||||
for i in 0 .. TG_nargs - 1 loop
|
||||
if i > 0 then
|
||||
argstr := argstr || ', ';
|
||||
end if;
|
||||
argstr := argstr || TG_argv[i];
|
||||
end loop;
|
||||
|
||||
RAISE NOTICE '%(%) % % % ON %',
|
||||
tg_name, argstr, TG_when, TG_level, TG_OP, relid;
|
||||
oldnew := '{}'::text[];
|
||||
if TG_OP != 'INSERT' then
|
||||
oldnew := array_append(oldnew, format('OLD: %s', OLD));
|
||||
end if;
|
||||
|
||||
if TG_OP != 'DELETE' then
|
||||
oldnew := array_append(oldnew, format('NEW: %s', NEW));
|
||||
end if;
|
||||
|
||||
RAISE NOTICE '%', array_to_string(oldnew, ',');
|
||||
|
||||
if TG_OP = 'DELETE' then
|
||||
return OLD;
|
||||
else
|
||||
return NEW;
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Test basic functionality
|
||||
CREATE TRIGGER trig_row_before
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
CREATE TRIGGER trig_row_after
|
||||
AFTER INSERT OR UPDATE OR DELETE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
delete from rem1;
|
||||
insert into rem1 values(1,'insert');
|
||||
update rem1 set f2 = 'update' where f1 = 1;
|
||||
update rem1 set f2 = f2 || f2;
|
||||
|
||||
|
||||
-- cleanup
|
||||
DROP TRIGGER trig_row_before ON rem1;
|
||||
DROP TRIGGER trig_row_after ON rem1;
|
||||
DROP TRIGGER trig_stmt_before ON rem1;
|
||||
DROP TRIGGER trig_stmt_after ON rem1;
|
||||
|
||||
DELETE from rem1;
|
||||
|
||||
|
||||
-- Test WHEN conditions
|
||||
|
||||
CREATE TRIGGER trig_row_before_insupd
|
||||
BEFORE INSERT OR UPDATE ON rem1
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.f2 like '%update%')
|
||||
EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
CREATE TRIGGER trig_row_after_insupd
|
||||
AFTER INSERT OR UPDATE ON rem1
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.f2 like '%update%')
|
||||
EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
-- Insert or update not matching: nothing happens
|
||||
INSERT INTO rem1 values(1, 'insert');
|
||||
UPDATE rem1 set f2 = 'test';
|
||||
|
||||
-- Insert or update matching: triggers are fired
|
||||
INSERT INTO rem1 values(2, 'update');
|
||||
UPDATE rem1 set f2 = 'update update' where f1 = '2';
|
||||
|
||||
CREATE TRIGGER trig_row_before_delete
|
||||
BEFORE DELETE ON rem1
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.f2 like '%update%')
|
||||
EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
CREATE TRIGGER trig_row_after_delete
|
||||
AFTER DELETE ON rem1
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.f2 like '%update%')
|
||||
EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
-- Trigger is fired for f1=2, not for f1=1
|
||||
DELETE FROM rem1;
|
||||
|
||||
-- cleanup
|
||||
DROP TRIGGER trig_row_before_insupd ON rem1;
|
||||
DROP TRIGGER trig_row_after_insupd ON rem1;
|
||||
DROP TRIGGER trig_row_before_delete ON rem1;
|
||||
DROP TRIGGER trig_row_after_delete ON rem1;
|
||||
|
||||
|
||||
-- Test various RETURN statements in BEFORE triggers.
|
||||
|
||||
CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.f2 := NEW.f2 || ' triggered !';
|
||||
RETURN NEW;
|
||||
END
|
||||
$$ language plpgsql;
|
||||
|
||||
CREATE TRIGGER trig_row_before_insupd
|
||||
BEFORE INSERT OR UPDATE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
|
||||
|
||||
-- The new values should have 'triggered' appended
|
||||
INSERT INTO rem1 values(1, 'insert');
|
||||
SELECT * from loc1;
|
||||
INSERT INTO rem1 values(2, 'insert') RETURNING f2;
|
||||
SELECT * from loc1;
|
||||
UPDATE rem1 set f2 = '';
|
||||
SELECT * from loc1;
|
||||
UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
|
||||
SELECT * from loc1;
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
UPDATE rem1 set f1 = 10; -- all columns should be transmitted
|
||||
UPDATE rem1 set f1 = 10;
|
||||
SELECT * from loc1;
|
||||
|
||||
DELETE FROM rem1;
|
||||
|
||||
-- Add a second trigger, to check that the changes are propagated correctly
|
||||
-- from trigger to trigger
|
||||
CREATE TRIGGER trig_row_before_insupd2
|
||||
BEFORE INSERT OR UPDATE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
|
||||
|
||||
INSERT INTO rem1 values(1, 'insert');
|
||||
SELECT * from loc1;
|
||||
INSERT INTO rem1 values(2, 'insert') RETURNING f2;
|
||||
SELECT * from loc1;
|
||||
UPDATE rem1 set f2 = '';
|
||||
SELECT * from loc1;
|
||||
UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
|
||||
SELECT * from loc1;
|
||||
|
||||
DROP TRIGGER trig_row_before_insupd ON rem1;
|
||||
DROP TRIGGER trig_row_before_insupd2 ON rem1;
|
||||
|
||||
DELETE from rem1;
|
||||
|
||||
INSERT INTO rem1 VALUES (1, 'test');
|
||||
|
||||
-- Test with a trigger returning NULL
|
||||
CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
RETURN NULL;
|
||||
END
|
||||
$$ language plpgsql;
|
||||
|
||||
CREATE TRIGGER trig_null
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trig_null();
|
||||
|
||||
-- Nothing should have changed.
|
||||
INSERT INTO rem1 VALUES (2, 'test2');
|
||||
|
||||
SELECT * from loc1;
|
||||
|
||||
UPDATE rem1 SET f2 = 'test2';
|
||||
|
||||
SELECT * from loc1;
|
||||
|
||||
DELETE from rem1;
|
||||
|
||||
SELECT * from loc1;
|
||||
|
||||
DROP TRIGGER trig_null ON rem1;
|
||||
DELETE from rem1;
|
||||
|
||||
-- Test a combination of local and remote triggers
|
||||
CREATE TRIGGER trig_row_before
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
CREATE TRIGGER trig_row_after
|
||||
AFTER INSERT OR UPDATE OR DELETE ON rem1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
||||
|
||||
CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1
|
||||
FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
|
||||
|
||||
INSERT INTO rem1(f2) VALUES ('test');
|
||||
UPDATE rem1 SET f2 = 'testo';
|
||||
|
||||
-- Test returning a system attribute
|
||||
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -581,4 +581,4 @@ test: gtt_clean
|
|||
|
||||
|
||||
# procedure, Function Test
|
||||
test: create_procedure create_function pg_compatibility
|
||||
test: create_procedure create_function pg_compatibility postgres_fdw
|
||||
|
|
|
|||
Loading…
Reference in New Issue