diff --git a/contrib/pgsql_fdw/connection.c b/contrib/pgsql_fdw/connection.c
index c971bd7..7cb448b 100644
*** a/contrib/pgsql_fdw/connection.c
--- b/contrib/pgsql_fdw/connection.c
*************** connect_pg_server(ForeignServer *server,
*** 293,299 ****
}
/*
! * Start transaction to use cursor to retrieve data separately.
*/
static void
begin_remote_tx(PGconn *conn)
--- 293,299 ----
}
/*
! * Start remote transaction with proper isolation level.
*/
static void
begin_remote_tx(PGconn *conn)
diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c
index 6f8f753..0f94551 100644
*** a/contrib/pgsql_fdw/deparse.c
--- b/contrib/pgsql_fdw/deparse.c
*************** void
*** 82,89 ****
deparseSimpleSql(StringInfo buf,
Oid relid,
PlannerInfo *root,
! RelOptInfo *baserel,
! ForeignTable *table)
{
StringInfoData foreign_relname;
bool first;
--- 82,88 ----
deparseSimpleSql(StringInfo buf,
Oid relid,
PlannerInfo *root,
! RelOptInfo *baserel)
{
StringInfoData foreign_relname;
bool first;
*************** deparseSimpleSql(StringInfo buf,
*** 173,179 ****
* deparse FROM clause, including alias if any
*/
appendStringInfo(buf, "FROM ");
! deparseRelation(buf, table->relid, root, true);
elog(DEBUG3, "Remote SQL: %s", buf->data);
}
--- 172,178 ----
* deparse FROM clause, including alias if any
*/
appendStringInfo(buf, "FROM ");
! deparseRelation(buf, relid, root, true);
elog(DEBUG3, "Remote SQL: %s", buf->data);
}
diff --git a/contrib/pgsql_fdw/expected/pgsql_fdw.out b/contrib/pgsql_fdw/expected/pgsql_fdw.out
index 8e50614..c486094 100644
*** a/contrib/pgsql_fdw/expected/pgsql_fdw.out
--- b/contrib/pgsql_fdw/expected/pgsql_fdw.out
*************** ALTER SERVER loopback1 OPTIONS (
*** 113,120 ****
);
ALTER SERVER loopback1 OPTIONS (user 'value'); -- ERROR
ERROR: invalid option "user"
! HINT: Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, requiressl, sslcompression, sslmode, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, gsslib, fetch_count
! ALTER SERVER loopback2 OPTIONS (ADD fetch_count '2');
ALTER USER MAPPING FOR public SERVER loopback1
OPTIONS (DROP user, DROP password);
ALTER USER MAPPING FOR public SERVER loopback1
--- 113,119 ----
);
ALTER SERVER loopback1 OPTIONS (user 'value'); -- ERROR
ERROR: invalid option "user"
! HINT: Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, requiressl, sslcompression, sslmode, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, gsslib
ALTER USER MAPPING FOR public SERVER loopback1
OPTIONS (DROP user, DROP password);
ALTER USER MAPPING FOR public SERVER loopback1
*************** ALTER USER MAPPING FOR public SERVER loo
*** 122,137 ****
ERROR: invalid option "host"
HINT: Valid options in this context are: user, password
ALTER FOREIGN TABLE ft1 OPTIONS (nspname 'S 1', relname 'T 1');
! ALTER FOREIGN TABLE ft2 OPTIONS (nspname 'S 1', relname 'T 1', fetch_count '100');
ALTER FOREIGN TABLE ft1 OPTIONS (invalid 'value'); -- ERROR
ERROR: invalid option "invalid"
! HINT: Valid options in this context are: nspname, relname, fetch_count
! ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count 'a'); -- ERROR
! ERROR: invalid value for fetch_count: "a"
! ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count '0'); -- ERROR
! ERROR: invalid value for fetch_count: "0"
! ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count '-1'); -- ERROR
! ERROR: invalid value for fetch_count: "-1"
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (invalid 'value'); -- ERROR
ERROR: invalid option "invalid"
HINT: Valid options in this context are: colname
--- 121,130 ----
ERROR: invalid option "host"
HINT: Valid options in this context are: user, password
ALTER FOREIGN TABLE ft1 OPTIONS (nspname 'S 1', relname 'T 1');
! ALTER FOREIGN TABLE ft2 OPTIONS (nspname 'S 1', relname 'T 1');
ALTER FOREIGN TABLE ft1 OPTIONS (invalid 'value'); -- ERROR
ERROR: invalid option "invalid"
! HINT: Valid options in this context are: nspname, relname
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (invalid 'value'); -- ERROR
ERROR: invalid option "invalid"
HINT: Valid options in this context are: colname
*************** ALTER FOREIGN TABLE ft2 ALTER COLUMN c1
*** 149,155 ****
Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW Options | Description
-----------+----------------+----------------------+-------------------+------+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------
loopback1 | pgsql_fdw_user | pgsql_fdw | | | | (authtype 'value', service 'value', connect_timeout 'value', dbname 'value', host 'value', hostaddr 'value', port 'value', tty 'value', options 'value', application_name 'value', keepalives 'value', keepalives_idle 'value', keepalives_interval 'value', sslcompression 'value', sslmode 'value', sslcert 'value', sslkey 'value', sslrootcert 'value', sslcrl 'value') |
! loopback2 | pgsql_fdw_user | pgsql_fdw | | | | (dbname 'contrib_regression', fetch_count '2') |
(2 rows)
\deu+
--- 142,148 ----
Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW Options | Description
-----------+----------------+----------------------+-------------------+------+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------
loopback1 | pgsql_fdw_user | pgsql_fdw | | | | (authtype 'value', service 'value', connect_timeout 'value', dbname 'value', host 'value', hostaddr 'value', port 'value', tty 'value', options 'value', application_name 'value', keepalives 'value', keepalives_idle 'value', keepalives_interval 'value', sslcompression 'value', sslmode 'value', sslcert 'value', sslkey 'value', sslrootcert 'value', sslcrl 'value') |
! loopback2 | pgsql_fdw_user | pgsql_fdw | | | | (dbname 'contrib_regression') |
(2 rows)
\deu+
*************** ALTER FOREIGN TABLE ft2 ALTER COLUMN c1
*** 161,189 ****
(2 rows)
\det+
! List of foreign tables
! Schema | Table | Server | FDW Options | Description
! --------+-------+-----------+---------------------------------------------------+-------------
! public | ft1 | loopback2 | (nspname 'S 1', relname 'T 1') |
! public | ft2 | loopback2 | (nspname 'S 1', relname 'T 1', fetch_count '100') |
(2 rows)
-- ===================================================================
-- simple queries
-- ===================================================================
-- single table, with/without alias
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------
Limit
- Output: c1, c2, c3, c4, c5, c6, c7
-> Sort
! Output: c1, c2, c3, c4, c5, c6, c7
! Sort Key: ft1.c3, ft1.c1
! -> Foreign Scan on public.ft1
! Output: c1, c2, c3, c4, c5, c6, c7
! Remote SQL: DECLARE pgsql_fdw_cursor_0 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (8 rows)
SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
c1 | c2 | c3 | c4 | c5 | c6 | c7
--- 154,179 ----
(2 rows)
\det+
! List of foreign tables
! Schema | Table | Server | FDW Options | Description
! --------+-------+-----------+--------------------------------+-------------
! public | ft1 | loopback2 | (nspname 'S 1', relname 'T 1') |
! public | ft2 | loopback2 | (nspname 'S 1', relname 'T 1') |
(2 rows)
-- ===================================================================
-- simple queries
-- ===================================================================
-- single table, with/without alias
! EXPLAIN (COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------
Limit
-> Sort
! Sort Key: c3, c1
! -> Foreign Scan on ft1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (5 rows)
SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
c1 | c2 | c3 | c4 | c5 | c6 | c7
*************** SELECT * FROM ft1 ORDER BY c3, c1 OFFSET
*** 200,217 ****
110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0
(10 rows)
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------
Limit
- Output: c1, c2, c3, c4, c5, c6, c7
-> Sort
! Output: c1, c2, c3, c4, c5, c6, c7
! Sort Key: t1.c3, t1.c1
! -> Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7
! Remote SQL: DECLARE pgsql_fdw_cursor_2 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (8 rows)
SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
c1 | c2 | c3 | c4 | c5 | c6 | c7
--- 190,204 ----
110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0
(10 rows)
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------
Limit
-> Sort
! Sort Key: c3, c1
! -> Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (5 rows)
SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
c1 | c2 | c3 | c4 | c5 | c6 | c7
*************** SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.
*** 229,242 ****
(10 rows)
-- with WHERE clause
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7
! Filter: (t1.c7 >= '1'::bpchar)
! Remote SQL: DECLARE pgsql_fdw_cursor_4 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 101)) AND (((c6)::text OPERATOR(pg_catalog.=) '1'::text))
! (4 rows)
SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
c1 | c2 | c3 | c4 | c5 | c6 | c7
--- 216,228 ----
(10 rows)
-- with WHERE clause
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on ft1 t1
! Filter: (c7 >= '1'::bpchar)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 101)) AND (((c6)::text OPERATOR(pg_catalog.=) '1'::text))
! (3 rows)
SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
c1 | c2 | c3 | c4 | c5 | c6 | c7
diff --git a/contrib/pgsql_fdw/option.c b/contrib/pgsql_fdw/option.c
index 542ef01..0192fd2 100644
*** a/contrib/pgsql_fdw/option.c
--- b/contrib/pgsql_fdw/option.c
***************
*** 26,37 ****
#include "pgsql_fdw.h"
/*
- * Default fetch count for cursor. This can be overridden by fetch_count FDW
- * option.
- */
- #define DEFAULT_FETCH_COUNT 10000
-
- /*
* SQL functions
*/
extern Datum pgsql_fdw_validator(PG_FUNCTION_ARGS);
--- 26,31 ----
*************** static PgsqlFdwOption valid_options[] =
*** 105,117 ****
{"relname", ForeignTableRelationId, false},
{"colname", AttributeRelationId, false},
- /*
- * Options for cursor behavior.
- * These options can be overridden by finer-grained objects.
- */
- {"fetch_count", ForeignTableRelationId, false},
- {"fetch_count", ForeignServerRelationId, false},
-
/* Terminating entry --- MUST BE LAST */
{NULL, InvalidOid, false}
};
--- 99,104 ----
*************** pgsql_fdw_validator(PG_FUNCTION_ARGS)
*** 165,184 ****
errhint("Valid options in this context are: %s",
buf.data)));
}
-
- /* fetch_count be positive digit number. */
- if (strcmp(def->defname, "fetch_count") == 0)
- {
- long value;
- char *p = NULL;
-
- value = strtol(defGetString(def), &p, 10);
- if (*p != '\0' || value < 1)
- ereport(ERROR,
- (errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE),
- errmsg("invalid value for %s: \"%s\"",
- def->defname, defGetString(def))));
- }
}
/*
--- 152,157 ----
*************** ExtractConnectionOptions(List *defelems,
*** 247,285 ****
return i;
}
- /*
- * Return fetch_count which should be used for the foreign table.
- */
- int
- GetFetchCountOption(ForeignTable *table, ForeignServer *server)
- {
- int fetch_count = DEFAULT_FETCH_COUNT;
- ListCell *lc;
- DefElem *def;
-
- /*
- * Use specified fetch_count instead of default value, if any. Foreign
- * table option overrides server option.
- */
- foreach(lc, table->options)
- {
- def = (DefElem *) lfirst(lc);
- if (strcmp(def->defname, "fetch_count") == 0)
- break;
-
- }
- if (lc == NULL)
- {
- foreach(lc, server->options)
- {
- def = (DefElem *) lfirst(lc);
- if (strcmp(def->defname, "fetch_count") == 0)
- break;
-
- }
- }
- if (lc != NULL)
- fetch_count = strtol(defGetString(def), NULL, 10);
-
- return fetch_count;
- }
--- 220,222 ----
diff --git a/contrib/pgsql_fdw/pgsql_fdw.c b/contrib/pgsql_fdw/pgsql_fdw.c
index 8975955..aede449 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.c
--- b/contrib/pgsql_fdw/pgsql_fdw.c
*************** PG_MODULE_MAGIC;
*** 45,59 ****
*/
#define TRANSFER_COSTS_PER_BYTE 0.001
- /*
- * Cursors which are used together in a local query require different name, so
- * we use simple incremental name for that purpose. We don't care wrap around
- * of cursor_id because it's hard to imagine that 2^32 cursors are used in a
- * query.
- */
- #define CURSOR_NAME_FORMAT "pgsql_fdw_cursor_%u"
- static uint32 cursor_id = 0;
-
/* Convenient macros for accessing the first record of PGresult. */
#define PGRES_VAL0(col) (PQgetvalue(res, 0, (col)))
#define PGRES_NULL0(col) (PQgetisnull(res, 0, (col)))
--- 45,50 ----
*************** typedef struct PgsqlFdwPlanState {
*** 85,111 ****
* Index of FDW-private information stored in fdw_private list.
*
* We store various information in ForeignScan.fdw_private to pass them beyond
! * the boundary between planner and executor. Finally FdwPlan using cursor
! * would hold items below:
*
* 1) plain SELECT statement
- * 2) SQL statement used to declare cursor
- * 3) SQL statement used to fetch rows from cursor
- * 4) SQL statement used to reset cursor
- * 5) SQL statement used to close cursor
*
* These items are indexed with the enum FdwPrivateIndex, so an item
! * can be accessed directly via list_nth(). For example of FETCH
! * statement:
! * list_nth(fdw_private, FdwPrivateFetchSql)
*/
enum FdwPrivateIndex {
/* SQL statements */
FdwPrivateSelectSql,
- FdwPrivateDeclareSql,
- FdwPrivateFetchSql,
- FdwPrivateResetSql,
- FdwPrivateCloseSql,
/* # of elements stored in the list fdw_private */
FdwPrivateNum,
--- 76,93 ----
* Index of FDW-private information stored in fdw_private list.
*
* We store various information in ForeignScan.fdw_private to pass them beyond
! * the boundary between planner and executor. Finally FdwPlan holds items
! * below:
*
* 1) plain SELECT statement
*
* These items are indexed with the enum FdwPrivateIndex, so an item
! * can be accessed directly via list_nth(). For example of SELECT statement:
! * sql = list_nth(fdw_private, FdwPrivateSelectSql)
*/
enum FdwPrivateIndex {
/* SQL statements */
FdwPrivateSelectSql,
/* # of elements stored in the list fdw_private */
FdwPrivateNum,
*************** typedef struct PgsqlFdwExecutionState
*** 132,137 ****
--- 114,120 ----
/* for storing result tuples */
MemoryContext scan_cxt; /* context for per-scan lifespan data */
+ MemoryContext temp_cxt; /* context for per-tuple temporary data */
Tuplestorestate *tuples; /* result of the scan */
/* for error handling. */
*************** static void get_remote_estimate(const ch
*** 178,185 ****
static void adjust_costs(double rows, int width,
Cost *startup_cost, Cost *total_cost);
static void execute_query(ForeignScanState *node);
! static PGresult *fetch_result(ForeignScanState *node);
! static void store_result(ForeignScanState *node, PGresult *res);
static void pgsql_fdw_error_callback(void *arg);
/* Exported functions, but not written in pgsql_fdw.h. */
--- 161,168 ----
static void adjust_costs(double rows, int width,
Cost *startup_cost, Cost *total_cost);
static void execute_query(ForeignScanState *node);
! static int query_row_processor(PGresult *res, const PGdataValue *columns,
! const char **errmsgp, void *param);
static void pgsql_fdw_error_callback(void *arg);
/* Exported functions, but not written in pgsql_fdw.h. */
*************** pgsqlGetForeignRelSize(PlannerInfo *root
*** 288,294 ****
* appended later.
*/
sortConditions(root, baserel, &remote_conds, ¶m_conds, &local_conds);
! deparseSimpleSql(sql, foreigntableid, root, baserel, table);
if (list_length(remote_conds) > 0)
{
appendWhereClause(sql, fpstate->has_where, remote_conds, root);
--- 271,277 ----
* appended later.
*/
sortConditions(root, baserel, &remote_conds, ¶m_conds, &local_conds);
! deparseSimpleSql(sql, foreigntableid, root, baserel);
if (list_length(remote_conds) > 0)
{
appendWhereClause(sql, fpstate->has_where, remote_conds, root);
*************** pgsqlGetForeignPlan(PlannerInfo *root,
*** 400,409 ****
PgsqlFdwPlanState *fpstate = (PgsqlFdwPlanState *) baserel->fdw_private;
Index scan_relid = baserel->relid;
List *fdw_private = NIL;
- char name[128]; /* must be larger than format + 10 */
- StringInfoData cursor;
- int fetch_count;
- char *sql;
List *fdw_exprs = NIL;
List *local_exprs = NIL;
ListCell *lc;
--- 383,388 ----
*************** pgsqlGetForeignPlan(PlannerInfo *root,
*** 420,454 ****
foreach(lc, fpstate->local_conds)
local_exprs = lappend(local_exprs,
((RestrictInfo *) lfirst(lc))->clause);
- fetch_count = GetFetchCountOption(fpstate->table, fpstate->server);
- elog(DEBUG1, "relid=%u fetch_count=%d", foreigntableid, fetch_count);
-
- /* Construct cursor name from sequential value */
- sprintf(name, CURSOR_NAME_FORMAT, cursor_id++);
/*
! * Construct CURSOR statements from plain remote query, and make a list
! * contains all of them to pass them to executor with plan node for later
! * use.
*/
! sql = fpstate->sql.data;
! fdw_private = lappend(fdw_private, makeString(sql));
!
! initStringInfo(&cursor);
! appendStringInfo(&cursor, "DECLARE %s SCROLL CURSOR FOR %s", name, sql);
! fdw_private = lappend(fdw_private, makeString(cursor.data));
!
! initStringInfo(&cursor);
! appendStringInfo(&cursor, "FETCH %d FROM %s", fetch_count, name);
! fdw_private = lappend(fdw_private, makeString(cursor.data));
!
! initStringInfo(&cursor);
! appendStringInfo(&cursor, "MOVE ABSOLUTE 0 FROM %s", name);
! fdw_private = lappend(fdw_private, makeString(cursor.data));
!
! initStringInfo(&cursor);
! appendStringInfo(&cursor, "CLOSE %s", name);
! fdw_private = lappend(fdw_private, makeString(cursor.data));
/*
* Create the ForeignScan node from target list, local filtering
--- 399,410 ----
foreach(lc, fpstate->local_conds)
local_exprs = lappend(local_exprs,
((RestrictInfo *) lfirst(lc))->clause);
/*
! * Make a list contains SELECT statement to it to executor with plan node
! * for later use.
*/
! fdw_private = lappend(fdw_private, makeString(fpstate->sql.data));
/*
* Create the ForeignScan node from target list, local filtering
*************** pgsqlExplainForeignScan(ForeignScanState
*** 477,488 ****
List *fdw_private;
char *sql;
- /* CURSOR declaration is shown in only VERBOSE mode. */
fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
! if (es->verbose)
! sql = strVal(list_nth(fdw_private, FdwPrivateDeclareSql));
! else
! sql = strVal(list_nth(fdw_private, FdwPrivateSelectSql));
ExplainPropertyText("Remote SQL", sql, es);
}
--- 433,440 ----
List *fdw_private;
char *sql;
fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
! sql = strVal(list_nth(fdw_private, FdwPrivateSelectSql));
ExplainPropertyText("Remote SQL", sql, es);
}
*************** pgsqlBeginForeignScan(ForeignScanState *
*** 514,523 ****
festate->fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
/*
! * Create context for per-scan tuplestore under per-query context.
*/
festate->scan_cxt = AllocSetContextCreate(node->ss.ps.state->es_query_cxt,
! "pgsql_fdw",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
--- 466,480 ----
festate->fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
/*
! * Create contexts for per-scan tuplestore under per-query context.
*/
festate->scan_cxt = AllocSetContextCreate(node->ss.ps.state->es_query_cxt,
! "pgsql_fdw per-scan data",
! ALLOCSET_DEFAULT_MINSIZE,
! ALLOCSET_DEFAULT_INITSIZE,
! ALLOCSET_DEFAULT_MAXSIZE);
! festate->temp_cxt = AllocSetContextCreate(node->ss.ps.state->es_query_cxt,
! "pgsql_fdw temporary data",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
*************** pgsqlIterateForeignScan(ForeignScanState
*** 586,705 ****
{
PgsqlFdwExecutionState *festate;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
- PGresult *res = NULL;
MemoryContext oldcontext = CurrentMemoryContext;
festate = (PgsqlFdwExecutionState *) node->fdw_state;
/*
! * If this is the first call after Begin, we need to execute remote query.
! *
! * Since we needs cursor to prevent out of memory, we declare a cursor at
! * the first call and fetch from it in later calls.
*/
if (festate->tuples == NULL)
execute_query(node);
/*
! * If enough tuples are left in tuplestore, just return next tuple from it.
*
* It is necessary to switch to per-scan context to make returned tuple
* valid until next IterateForeignScan call, because it will be released
* with ExecClearTuple then. Otherwise, picked tuple is allocated in
* per-tuple context, and double-free of that tuple might happen.
*/
MemoryContextSwitchTo(festate->scan_cxt);
! if (tuplestore_gettupleslot(festate->tuples, true, false, slot))
! {
! MemoryContextSwitchTo(oldcontext);
! return slot;
! }
! MemoryContextSwitchTo(oldcontext);
!
! /*
! * Here we need to clear partial result and fetch next bunch of tuples from
! * from the cursor for the scan. If the fetch returns no tuple, the scan
! * has reached the end.
! *
! * PGresult must be released before leaving this function.
! */
! PG_TRY();
! {
! res = fetch_result(node);
! store_result(node, res);
! PQclear(res);
! res = NULL;
! }
! PG_CATCH();
! {
! PQclear(res);
! PG_RE_THROW();
! }
! PG_END_TRY();
!
! /*
! * If we got more tuples from the server cursor, return next tuple from
! * tuplestore.
! */
! MemoryContextSwitchTo(festate->scan_cxt);
! if (tuplestore_gettupleslot(festate->tuples, true, false, slot))
! {
! MemoryContextSwitchTo(oldcontext);
! return slot;
! }
MemoryContextSwitchTo(oldcontext);
- /* We don't have any result even in remote server cursor. */
- ExecClearTuple(slot);
return slot;
}
/*
* pgsqlReScanForeignScan
! * - Restart this scan by resetting fetch location.
*/
static void
pgsqlReScanForeignScan(ForeignScanState *node)
{
- List *fdw_private;
- char *sql;
- PGconn *conn;
- PGresult *res = NULL;
PgsqlFdwExecutionState *festate;
festate = (PgsqlFdwExecutionState *) node->fdw_state;
! /* If we have not opened cursor yet, nothing to do. */
if (festate->tuples == NULL)
return;
! /* Discard fetch results if any. */
! tuplestore_clear(festate->tuples);
!
! /* PGresult must be released before leaving this function. */
! PG_TRY();
! {
! /* Reset cursor */
! fdw_private = festate->fdw_private;
! conn = festate->conn;
! sql = strVal(list_nth(fdw_private, FdwPrivateResetSql));
! res = PQexec(conn, sql);
! if (PQresultStatus(res) != PGRES_COMMAND_OK)
! {
! ereport(ERROR,
! (errmsg("could not rewind cursor"),
! errdetail("%s", PQerrorMessage(conn)),
! errhint("%s", sql)));
! }
! PQclear(res);
! res = NULL;
! }
! PG_CATCH();
! {
! PQclear(res);
! PG_RE_THROW();
! }
! PG_END_TRY();
}
/*
--- 543,596 ----
{
PgsqlFdwExecutionState *festate;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
MemoryContext oldcontext = CurrentMemoryContext;
festate = (PgsqlFdwExecutionState *) node->fdw_state;
/*
! * If this is the first call after Begin or ReScan, we need to execute
! * remote query and get result set.
*/
if (festate->tuples == NULL)
execute_query(node);
/*
! * If tuples are still left in tuplestore, just return next tuple from it.
*
* It is necessary to switch to per-scan context to make returned tuple
* valid until next IterateForeignScan call, because it will be released
* with ExecClearTuple then. Otherwise, picked tuple is allocated in
* per-tuple context, and double-free of that tuple might happen.
+ *
+ * If we don't have any result in tuplestore, clear result slot to tell
+ * executor that this scan is over.
*/
MemoryContextSwitchTo(festate->scan_cxt);
! tuplestore_gettupleslot(festate->tuples, true, false, slot);
MemoryContextSwitchTo(oldcontext);
return slot;
}
/*
* pgsqlReScanForeignScan
! * - Restart this scan by clearing old results and set re-execute flag.
*/
static void
pgsqlReScanForeignScan(ForeignScanState *node)
{
PgsqlFdwExecutionState *festate;
festate = (PgsqlFdwExecutionState *) node->fdw_state;
! /* If we haven't have valid result yet, nothing to do. */
if (festate->tuples == NULL)
return;
! /*
! * Only rewind the current result set is enough.
! */
! tuplestore_rescan(festate->tuples);
}
/*
*************** pgsqlReScanForeignScan(ForeignScanState
*** 709,718 ****
static void
pgsqlEndForeignScan(ForeignScanState *node)
{
- List *fdw_private;
- char *sql;
- PGconn *conn;
- PGresult *res = NULL;
PgsqlFdwExecutionState *festate;
festate = (PgsqlFdwExecutionState *) node->fdw_state;
--- 600,605 ----
*************** pgsqlEndForeignScan(ForeignScanState *no
*** 721,763 ****
if (festate == NULL)
return;
! /* If we have not opened cursor yet, nothing to do. */
! if (festate->tuples == NULL)
! return;
/* Discard fetch results */
! tuplestore_end(festate->tuples);
! festate->tuples = NULL;
!
! /* PGresult must be released before leaving this function. */
! PG_TRY();
! {
! /* Close cursor */
! fdw_private = festate->fdw_private;
! conn = festate->conn;
! sql = strVal(list_nth(fdw_private, FdwPrivateCloseSql));
! res = PQexec(conn, sql);
! if (PQresultStatus(res) != PGRES_COMMAND_OK)
! {
! ereport(ERROR,
! (errmsg("could not close cursor"),
! errdetail("%s", PQerrorMessage(conn)),
! errhint("%s", sql)));
! }
! PQclear(res);
! res = NULL;
! }
! PG_CATCH();
{
! PQclear(res);
! PG_RE_THROW();
}
- PG_END_TRY();
-
- ReleaseConnection(festate->conn);
! MemoryContextDelete(festate->scan_cxt);
! festate->scan_cxt = NULL;
}
/*
--- 608,629 ----
if (festate == NULL)
return;
! /*
! * The connection which was used for this scan should be valid until the
! * end of the scan to make the lifespan of remote transaction same as the
! * local query.
! */
! ReleaseConnection(festate->conn);
! festate->conn = NULL;
/* Discard fetch results */
! if (festate->tuples != NULL)
{
! tuplestore_end(festate->tuples);
! festate->tuples = NULL;
}
! /* MemoryContext will be deleted automatically. */
}
/*
*************** adjust_costs(double rows, int width, Cos
*** 848,854 ****
static void
execute_query(ForeignScanState *node)
{
- List *fdw_private;
PgsqlFdwExecutionState *festate;
ParamListInfo params = node->ss.ps.state->es_param_list_info;
int numParams = params ? params->numParams : 0;
--- 714,719 ----
*************** execute_query(ForeignScanState *node)
*** 893,925 ****
PG_TRY();
{
/*
! * Execute remote query with parameters.
*/
conn = festate->conn;
! fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
! sql = strVal(list_nth(fdw_private, FdwPrivateDeclareSql));
res = PQexecParams(conn, sql, numParams, types, values, NULL, NULL, 0);
/*
* If the query has failed, reporting details is enough here.
* Connection(s) which are used by this query (at least used by
* pgsql_fdw) will be cleaned up by the foreign connection manager.
*/
! if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
ereport(ERROR,
! (errmsg("could not declare cursor"),
errdetail("%s", PQerrorMessage(conn)),
errhint("%s", sql)));
}
! /* Discard result of DECLARE statement. */
! PQclear(res);
! res = NULL;
!
! /* Fetch first bunch of the result and store them into tuplestore. */
! res = fetch_result(node);
! store_result(node, res);
PQclear(res);
res = NULL;
}
--- 758,795 ----
PG_TRY();
{
/*
! * Execute remote query with parameters, and retrieve results with
! * custom row processor which stores results in tuplestore.
! *
! * We uninstall the custom row processor right after processing all
! * results.
*/
conn = festate->conn;
! sql = strVal(list_nth(festate->fdw_private, FdwPrivateSelectSql));
! PQsetRowProcessor(conn, query_row_processor, node);
res = PQexecParams(conn, sql, numParams, types, values, NULL, NULL, 0);
+ PQsetRowProcessor(conn, NULL, NULL);
+
+ /*
+ * We can't know whether the scan is over or not in custom row
+ * processor, so mark that the result is valid here.
+ */
+ tuplestore_donestoring(festate->tuples);
/*
* If the query has failed, reporting details is enough here.
* Connection(s) which are used by this query (at least used by
* pgsql_fdw) will be cleaned up by the foreign connection manager.
*/
! if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
ereport(ERROR,
! (errmsg("could not execute remote query"),
errdetail("%s", PQerrorMessage(conn)),
errhint("%s", sql)));
}
! /* Discard result of SELECT statement. */
PQclear(res);
res = NULL;
}
*************** execute_query(ForeignScanState *node)
*** 932,1098 ****
}
/*
- * Fetch next partial result from remote server.
- *
- * Once this function has returned result records as PGresult, caller is
- * responsible to release it, so caller should put codes which might throw
- * exception in PG_TRY block. When an exception has been caught, release
- * PGresult and re-throw the exception in PG_CATCH block.
- */
- static PGresult *
- fetch_result(ForeignScanState *node)
- {
- PgsqlFdwExecutionState *festate;
- List *fdw_private;
- char *sql;
- PGconn *conn;
- PGresult *res;
-
- festate = (PgsqlFdwExecutionState *) node->fdw_state;
-
- /* Retrieve information for fetching result. */
- fdw_private = festate->fdw_private;
- sql = strVal(list_nth(fdw_private, FdwPrivateFetchSql));
- conn = festate->conn;
-
- /*
- * Fetch result from remote server. In error case, we must release
- * PGresult in this function to avoid memory leak because caller can't
- * get the reference.
- */
- res = PQexec(conn, sql);
- if (PQresultStatus(res) != PGRES_TUPLES_OK)
- {
- PQclear(res);
- ereport(ERROR,
- (errmsg("could not fetch rows from foreign server"),
- errdetail("%s", PQerrorMessage(conn)),
- errhint("%s", sql)));
- }
-
- return res;
- }
-
- /*
* Create tuples from PGresult and store them into tuplestore.
*
* Caller must use PG_TRY block to catch exception and release PGresult
* surely.
*/
! static void
! store_result(ForeignScanState *node, PGresult *res)
{
! int rows;
! int row;
! int i;
! int nfields;
! int attnum; /* number of non-dropped columns */
! Form_pg_attribute *attrs;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
TupleDesc tupdesc = slot->tts_tupleDescriptor;
! PgsqlFdwExecutionState *festate;
! AttInMetadata *attinmeta;
ErrorContextCallback errcontext;
! festate = (PgsqlFdwExecutionState *) node->fdw_state;
! rows = PQntuples(res);
! nfields = PQnfields(res);
! attrs = tupdesc->attrs;
! attinmeta = festate->attinmeta;
!
! /* First, ensure that the tuplestore is empty. */
! if (festate->tuples == NULL)
{
! MemoryContext oldcontext = CurrentMemoryContext;
! /*
! * Create tuplestore to store result of the query in per-query context.
! * Note that we use this memory context to avoid memory leak in error
! * cases.
! */
! MemoryContextSwitchTo(festate->scan_cxt);
! festate->tuples = tuplestore_begin_heap(false, false, work_mem);
! MemoryContextSwitchTo(oldcontext);
! }
! else
! {
! /* We already have tuplestore, just need to clear contents of it. */
! tuplestore_clear(festate->tuples);
! }
! /* count non-dropped columns */
! for (attnum = 0, i = 0; i < tupdesc->natts; i++)
! if (!attrs[i]->attisdropped)
! attnum++;
! /* check result and tuple descriptor have the same number of columns */
! if (attnum > 0 && attnum != nfields)
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("remote query result rowtype does not match "
! "the specified FROM clause rowtype"),
! errdetail("expected %d, actual %d", attnum, nfields)));
/*
! * Set up callback to identify error column. We don't set callback right
! * row because it should be set only during column value conversion.
*/
! errcontext.callback = pgsql_fdw_error_callback;
! errcontext.arg = (void *) festate;
! /* put a tuples into the slot */
! for (row = 0; row < rows; row++)
{
! int j;
! HeapTuple tuple;
! /* Install callback function for error. */
! errcontext.previous = error_context_stack;
! error_context_stack = &errcontext;
! for (i = 0, j = 0; i < tupdesc->natts; i++)
{
! /* skip dropped columns. */
! if (attrs[i]->attisdropped)
{
! festate->nulls[i] = true;
! continue;
}
/*
! * Set NULL indicator, and convert text representation to internal
! * representation if any.
*/
! if (PQgetisnull(res, row, j))
! festate->nulls[i] = true;
! else
! {
! Datum value;
! festate->cur_attno = i + 1; /* first attribute has index 1 */
! festate->nulls[i] = false;
! value = InputFunctionCall(&attinmeta->attinfuncs[i],
! PQgetvalue(res, row, j),
! attinmeta->attioparams[i],
! attinmeta->atttypmods[i]);
! festate->values[i] = value;
! }
! j++;
}
! /* Uninstall error callback function. */
! error_context_stack = errcontext.previous;
! /*
! * Build the tuple and put it into the slot.
! * We don't have to free the tuple explicitly because it's been
! * allocated in the per-tuple context.
! */
! tuple = heap_form_tuple(tupdesc, festate->values, festate->nulls);
! tuplestore_puttuple(festate->tuples, tuple);
! }
! tuplestore_donestoring(festate->tuples);
}
/*
--- 802,954 ----
}
/*
* Create tuples from PGresult and store them into tuplestore.
*
* Caller must use PG_TRY block to catch exception and release PGresult
* surely.
*/
! static int
! query_row_processor(PGresult *res,
! const PGdataValue *columns,
! const char **errmsgp,
! void *param)
{
! ForeignScanState *node = (ForeignScanState *) param;
! int nfields = PQnfields(res);
! int i;
! int j;
! int attnum; /* number of non-dropped columns */
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
TupleDesc tupdesc = slot->tts_tupleDescriptor;
! Form_pg_attribute *attrs = tupdesc->attrs;
! PgsqlFdwExecutionState *festate = (PgsqlFdwExecutionState *) node->fdw_state;
! AttInMetadata *attinmeta = festate->attinmeta;
! char *colbuf;
! int colbuflen;
! HeapTuple tuple;
ErrorContextCallback errcontext;
+ MemoryContext oldcontext;
! if (columns == NULL)
{
! /* count non-dropped columns */
! for (attnum = 0, i = 0; i < tupdesc->natts; i++)
! if (!attrs[i]->attisdropped)
! attnum++;
! /* check result and tuple descriptor have the same number of columns */
! if (attnum > 0 && attnum != nfields)
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("remote query result rowtype does not match "
! "the specified FROM clause rowtype"),
! errdetail("expected %d, actual %d", attnum, nfields)));
! /* First, ensure that the tuplestore is empty. */
! if (festate->tuples == NULL)
! {
! /*
! * Create tuplestore to store result of the query in per-query
! * context. Note that we use this memory context to avoid memory
! * leak in error cases.
! */
! oldcontext = MemoryContextSwitchTo(festate->scan_cxt);
! festate->tuples = tuplestore_begin_heap(false, false, work_mem);
! MemoryContextSwitchTo(oldcontext);
! }
! else
! {
! /* Clear old result just in case. */
! tuplestore_clear(festate->tuples);
! }
!
! return 1;
! }
/*
! * This function is called repeatedly until all result rows are processed,
! * so we should allow interrupt.
*/
! CHECK_FOR_INTERRUPTS();
! /*
! * Do the following work in a temp context that we reset after each tuple.
! * This cleans up not only the data we have direct access to, but any
! * cruft the I/O functions might leak.
! */
! oldcontext = MemoryContextSwitchTo(festate->temp_cxt);
!
! /* Initialize column value buffer. */
! colbuflen = 1024;
! colbuf = palloc(colbuflen);
!
! for (i = 0, j = 0; i < tupdesc->natts; i++)
{
! int len = columns[j].len;
! /* skip dropped columns. */
! if (attrs[i]->attisdropped)
! {
! festate->nulls[i] = true;
! continue;
! }
! /*
! * Set NULL indicator, and convert text representation to internal
! * representation if any.
! */
! if (len < 0)
! festate->nulls[i] = true;
! else
{
! Datum value;
!
! festate->nulls[i] = false;
!
! while (colbuflen < len + 1)
{
! colbuflen *= 2;
! colbuf = repalloc(colbuf, colbuflen);
}
+ memcpy(colbuf, columns[j].value, len);
+ colbuf[columns[j].len] = '\0';
/*
! * Set up and install callback to report where convertion error
! * occurs.
*/
! festate->cur_attno = i + 1;
! errcontext.callback = pgsql_fdw_error_callback;
! errcontext.arg = (void *) festate;
! errcontext.previous = error_context_stack;
! error_context_stack = &errcontext;
! value = InputFunctionCall(&attinmeta->attinfuncs[i],
! colbuf,
! attinmeta->attioparams[i],
! attinmeta->atttypmods[i]);
! festate->values[i] = value;
!
! /* Uninstall error context callback. */
! error_context_stack = errcontext.previous;
}
+ j++;
+ }
! /*
! * Build the tuple and put it into the slot.
! * We don't have to free the tuple explicitly because it's been
! * allocated in the per-tuple context.
! */
! tuple = heap_form_tuple(tupdesc, festate->values, festate->nulls);
! tuplestore_puttuple(festate->tuples, tuple);
! /* Clean up */
! MemoryContextSwitchTo(oldcontext);
! MemoryContextReset(festate->temp_cxt);
! return 1;
}
/*
diff --git a/contrib/pgsql_fdw/pgsql_fdw.h b/contrib/pgsql_fdw/pgsql_fdw.h
index 5487d52..69b3b2d 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.h
--- b/contrib/pgsql_fdw/pgsql_fdw.h
*************** int GetFetchCountOption(ForeignTable *ta
*** 29,36 ****
void deparseSimpleSql(StringInfo buf,
Oid relid,
PlannerInfo *root,
! RelOptInfo *baserel,
! ForeignTable *table);
void appendWhereClause(StringInfo buf,
bool has_where,
List *exprs,
--- 29,35 ----
void deparseSimpleSql(StringInfo buf,
Oid relid,
PlannerInfo *root,
! RelOptInfo *baserel);
void appendWhereClause(StringInfo buf,
bool has_where,
List *exprs,
diff --git a/contrib/pgsql_fdw/sql/pgsql_fdw.sql b/contrib/pgsql_fdw/sql/pgsql_fdw.sql
index 6692af7..6e43ea5 100644
*** a/contrib/pgsql_fdw/sql/pgsql_fdw.sql
--- b/contrib/pgsql_fdw/sql/pgsql_fdw.sql
*************** ALTER SERVER loopback1 OPTIONS (
*** 121,137 ****
--replication 'value'
);
ALTER SERVER loopback1 OPTIONS (user 'value'); -- ERROR
- ALTER SERVER loopback2 OPTIONS (ADD fetch_count '2');
ALTER USER MAPPING FOR public SERVER loopback1
OPTIONS (DROP user, DROP password);
ALTER USER MAPPING FOR public SERVER loopback1
OPTIONS (host 'value'); -- ERROR
ALTER FOREIGN TABLE ft1 OPTIONS (nspname 'S 1', relname 'T 1');
! ALTER FOREIGN TABLE ft2 OPTIONS (nspname 'S 1', relname 'T 1', fetch_count '100');
ALTER FOREIGN TABLE ft1 OPTIONS (invalid 'value'); -- ERROR
- ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count 'a'); -- ERROR
- ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count '0'); -- ERROR
- ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count '-1'); -- ERROR
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (invalid 'value'); -- ERROR
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (colname 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (colname 'C 1');
--- 121,133 ----
--replication 'value'
);
ALTER SERVER loopback1 OPTIONS (user 'value'); -- ERROR
ALTER USER MAPPING FOR public SERVER loopback1
OPTIONS (DROP user, DROP password);
ALTER USER MAPPING FOR public SERVER loopback1
OPTIONS (host 'value'); -- ERROR
ALTER FOREIGN TABLE ft1 OPTIONS (nspname 'S 1', relname 'T 1');
! ALTER FOREIGN TABLE ft2 OPTIONS (nspname 'S 1', relname 'T 1');
ALTER FOREIGN TABLE ft1 OPTIONS (invalid 'value'); -- ERROR
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (invalid 'value'); -- ERROR
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (colname 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (colname 'C 1');
*************** ALTER FOREIGN TABLE ft2 ALTER COLUMN c1
*** 144,155 ****
-- simple queries
-- ===================================================================
-- single table, with/without alias
! EXPLAIN (VERBOSE, 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;
-- 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';
-- aggregate
SELECT COUNT(*) FROM ft1 t1;
--- 140,151 ----
-- 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 (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;
-- with WHERE clause
! EXPLAIN (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';
-- aggregate
SELECT COUNT(*) FROM ft1 t1;
diff --git a/doc/src/sgml/pgsql-fdw.sgml b/doc/src/sgml/pgsql-fdw.sgml
index eb69f01..ee9c94a 100644
*** a/doc/src/sgml/pgsql-fdw.sgml
--- b/doc/src/sgml/pgsql-fdw.sgml
***************
*** 97,129 ****
-
- Cursor Options
-
- The pgsql_fdw always uses cursor to retrieve the
- result from external server. Users can control the behavior of cursor by
- setting cursor options to foreign table or foreign server. If an option
- is set to both objects, finer-grained setting is used. In other words,
- foreign table's setting overrides foreign server's setting.
-
-
-
-
-
- fetch_count
-
-
- This option specifies the number of rows to be fetched at a time.
- This option accepts only integer value larger than zero. The default
- setting is 10000.
-
-
-
-
-
-
-
-
--- 97,102 ----
*************** postgres=# EXPLAIN SELECT aid FROM pgben
*** 249,258 ****
Remote SQL: SELECT aid, NULL, abalance, NULL FROM public.pgbench_accounts
(3 rows)
-
- When you specify VERBOSE> option, you can see actual DECLARE
- statement with cursor name which is used for the scan.
-
--- 222,227 ----