pgsql_fdw_rowproc_v1.patch

text/plain

Filename: pgsql_fdw_rowproc_v1.patch
Type: text/plain
Part: 0
Message: Re: pgsql_fdw, FDW for PostgreSQL server

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v1
File+
contrib/pgsql_fdw/connection.c 0 0
contrib/pgsql_fdw/deparse.c 0 0
contrib/pgsql_fdw/expected/pgsql_fdw.out 0 0
contrib/pgsql_fdw/option.c 0 0
contrib/pgsql_fdw/pgsql_fdw.c 0 0
contrib/pgsql_fdw/pgsql_fdw.h 0 0
contrib/pgsql_fdw/sql/pgsql_fdw.sql 0 0
doc/src/sgml/pgsql-fdw.sgml 0 0
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, &param_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, &param_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 ****
  
    </sect3>
  
-   <sect3>
-    <title>Cursor Options</title>
-    <para>
-     The <application>pgsql_fdw</application> 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.
-    </para>
- 
-    <variablelist>
- 
-     <varlistentry>
-      <term><literal>fetch_count</literal></term>
-      <listitem>
-       <para>
-        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.
-       </para>
-      </listitem>
-     </varlistentry>
- 
-    </variablelist>
- 
-   </sect3>
- 
   </sect2>
  
   <sect2>
--- 97,102 ----
*************** postgres=# EXPLAIN SELECT aid FROM pgben
*** 249,258 ****
     Remote SQL: SELECT aid, NULL, abalance, NULL FROM public.pgbench_accounts
  (3 rows)
  </synopsis>
-   <para>
-    When you specify <literal>VERBOSE</> option, you can see actual DECLARE
-    statement with cursor name which is used for the scan.
-   </para>
   </sect2>
  
   <sect2>
--- 222,227 ----