postgres_fdw: Allow fetch_size to be set per-table or per-server.
authorRobert Haas <[email protected]>
Wed, 3 Feb 2016 14:01:59 +0000 (09:01 -0500)
committerRobert Haas <[email protected]>
Wed, 3 Feb 2016 14:07:35 +0000 (09:07 -0500)
The default fetch size of 100 rows might not be right in every
environment, so allow users to configure it.

Corey Huinker, reviewed by Kyotaro Horiguchi, Andres Freund, and me.

contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/option.c
contrib/postgres_fdw/postgres_fdw.c
contrib/postgres_fdw/postgres_fdw.h
contrib/postgres_fdw/sql/postgres_fdw.sql
doc/src/sgml/postgres-fdw.sgml

index 2390e61c2c4798a314e2306dd9280c15069e795e..e28cf7722e47686d27b2181fedce8e6ff5e782d7 100644 (file)
@@ -3951,3 +3951,63 @@ QUERY:  CREATE FOREIGN TABLE t5 (
 OPTIONS (schema_name 'import_source', table_name 't5');
 CONTEXT:  importing foreign table "t5"
 ROLLBACK;
+BEGIN;
+CREATE SERVER fetch101 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( fetch_size '101' );
+SELECT count(*)
+FROM pg_foreign_server
+WHERE srvname = 'fetch101'
+AND srvoptions @> array['fetch_size=101'];
+ count 
+-------
+     1
+(1 row)
+
+ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' );
+SELECT count(*)
+FROM pg_foreign_server
+WHERE srvname = 'fetch101'
+AND srvoptions @> array['fetch_size=101'];
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*)
+FROM pg_foreign_server
+WHERE srvname = 'fetch101'
+AND srvoptions @> array['fetch_size=202'];
+ count 
+-------
+     1
+(1 row)
+
+CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' );
+SELECT COUNT(*)
+FROM pg_foreign_table
+WHERE ftrelid = 'table30000'::regclass
+AND ftoptions @> array['fetch_size=30000'];
+ count 
+-------
+     1
+(1 row)
+
+ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000');
+SELECT COUNT(*)
+FROM pg_foreign_table
+WHERE ftrelid = 'table30000'::regclass
+AND ftoptions @> array['fetch_size=30000'];
+ count 
+-------
+     0
+(1 row)
+
+SELECT COUNT(*)
+FROM pg_foreign_table
+WHERE ftrelid = 'table30000'::regclass
+AND ftoptions @> array['fetch_size=60000'];
+ count 
+-------
+     1
+(1 row)
+
+ROLLBACK;
index 86a57890fc09b393cf5587056812d181332e92e0..f89de2f694714bdea2ebdb1e0af593d504f420e4 100644 (file)
@@ -131,6 +131,17 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
            /* check list syntax, warn about uninstalled extensions */
            (void) ExtractExtensionList(defGetString(def), true);
        }
+       else if (strcmp(def->defname, "fetch_size") == 0)
+       {
+           int     fetch_size;
+
+           fetch_size = strtol(defGetString(def), NULL,10);
+           if (fetch_size <= 0)
+               ereport(ERROR,
+                       (errcode(ERRCODE_SYNTAX_ERROR),
+                        errmsg("%s requires a non-negative integer value",
+                               def->defname)));
+       }
    }
 
    PG_RETURN_VOID();
@@ -162,6 +173,9 @@ InitPgFdwOptions(void)
        /* updatable is available on both server and table */
        {"updatable", ForeignServerRelationId, false},
        {"updatable", ForeignTableRelationId, false},
+       /* fetch_size is available on both server and table */
+       {"fetch_size", ForeignServerRelationId, false},
+       {"fetch_size", ForeignTableRelationId, false},
        {NULL, InvalidOid, false}
    };
 
index 2ab85f68a7507c8ae5885e03c558fbaa659df71c..d5c038335aee9113de24c1d5e2bd8ea9aa1dcace 100644 (file)
@@ -68,7 +68,9 @@ enum FdwScanPrivateIndex
    /* SQL statement to execute remotely (as a String node) */
    FdwScanPrivateSelectSql,
    /* Integer list of attribute numbers retrieved by the SELECT */
-   FdwScanPrivateRetrievedAttrs
+   FdwScanPrivateRetrievedAttrs,
+   /* Integer representing the desired fetch_size */
+   FdwScanPrivateFetchSize
 };
 
 /*
@@ -126,6 +128,8 @@ typedef struct PgFdwScanState
    /* working memory contexts */
    MemoryContext batch_cxt;    /* context holding current batch of tuples */
    MemoryContext temp_cxt;     /* context for per-tuple temporary data */
+
+   int         fetch_size;     /* number of tuples per fetch */
 } PgFdwScanState;
 
 /*
@@ -380,6 +384,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
    fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
    fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
    fpinfo->shippable_extensions = NIL;
+   fpinfo->fetch_size = 100;
 
    foreach(lc, fpinfo->server->options)
    {
@@ -394,16 +399,17 @@ postgresGetForeignRelSize(PlannerInfo *root,
        else if (strcmp(def->defname, "extensions") == 0)
            fpinfo->shippable_extensions =
                ExtractExtensionList(defGetString(def), false);
+       else if (strcmp(def->defname, "fetch_size") == 0)
+           fpinfo->fetch_size = strtol(defGetString(def), NULL,10);
    }
    foreach(lc, fpinfo->table->options)
    {
        DefElem    *def = (DefElem *) lfirst(lc);
 
        if (strcmp(def->defname, "use_remote_estimate") == 0)
-       {
            fpinfo->use_remote_estimate = defGetBoolean(def);
-           break;              /* only need the one value */
-       }
+       else if (strcmp(def->defname, "fetch_size") == 0)
+           fpinfo->fetch_size = strtol(defGetString(def), NULL,10);
    }
 
    /*
@@ -1012,6 +1018,9 @@ postgresGetForeignPlan(PlannerInfo *root,
     */
    fdw_private = list_make2(makeString(sql.data),
                             retrieved_attrs);
+   fdw_private = list_make3(makeString(sql.data),
+                            retrieved_attrs,
+                            makeInteger(fpinfo->fetch_size));
 
    /*
     * Create the ForeignScan node from target list, filtering expressions,
@@ -1088,6 +1097,8 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
                                     FdwScanPrivateSelectSql));
    fsstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private,
                                               FdwScanPrivateRetrievedAttrs);
+   fsstate->fetch_size = intVal(list_nth(fsplan->fdw_private,
+                                FdwScanPrivateFetchSize));
 
    /* Create contexts for batches of tuples and per-tuple temp workspace. */
    fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
@@ -2214,15 +2225,11 @@ fetch_more_data(ForeignScanState *node)
    {
        PGconn     *conn = fsstate->conn;
        char        sql[64];
-       int         fetch_size;
        int         numrows;
        int         i;
 
-       /* The fetch size is arbitrary, but shouldn't be enormous. */
-       fetch_size = 100;
-
        snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
-                fetch_size, fsstate->cursor_number);
+                fsstate->fetch_size, fsstate->cursor_number);
 
        res = PQexec(conn, sql);
        /* On error, report the original query, not the FETCH. */
@@ -2250,7 +2257,7 @@ fetch_more_data(ForeignScanState *node)
            fsstate->fetch_ct_2++;
 
        /* Must be EOF if we didn't get as many tuples as we asked for. */
-       fsstate->eof_reached = (numrows < fetch_size);
+       fsstate->eof_reached = (numrows < fsstate->fetch_size);
 
        PQclear(res);
        res = NULL;
@@ -2563,6 +2570,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 {
    PgFdwAnalyzeState astate;
    ForeignTable *table;
+   ForeignServer *server;
    UserMapping *user;
    PGconn     *conn;
    unsigned int cursor_number;
@@ -2593,6 +2601,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
     * owner, even if the ANALYZE was started by some other user.
     */
    table = GetForeignTable(RelationGetRelid(relation));
+   server = GetForeignServer(table->serverid);
    user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
    conn = GetConnection(user, false);
 
@@ -2620,6 +2629,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
            int         fetch_size;
            int         numrows;
            int         i;
+           ListCell   *lc;
 
            /* Allow users to cancel long query */
            CHECK_FOR_INTERRUPTS();
@@ -2632,6 +2642,26 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 
            /* The fetch size is arbitrary, but shouldn't be enormous. */
            fetch_size = 100;
+           foreach(lc, server->options)
+           {
+               DefElem    *def = (DefElem *) lfirst(lc);
+
+               if (strcmp(def->defname, "fetch_size") == 0)
+               {
+                   fetch_size = strtol(defGetString(def), NULL,10);
+                   break;
+               }
+           }
+           foreach(lc, table->options)
+           {
+               DefElem    *def = (DefElem *) lfirst(lc);
+
+               if (strcmp(def->defname, "fetch_size") == 0)
+               {
+                   fetch_size = strtol(defGetString(def), NULL,10);
+                   break;
+               }
+           }
 
            /* Fetch some rows */
            snprintf(fetch_sql, sizeof(fetch_sql), "FETCH %d FROM c%u",
index bf83c91481cc7bb5e913d7ce554683bc7a51bae6..2b632817fecbeb6adaae31ca2a68fd6903ca655a 100644 (file)
@@ -53,6 +53,8 @@ typedef struct PgFdwRelationInfo
    ForeignTable *table;
    ForeignServer *server;
    UserMapping *user;          /* only set in use_remote_estimate mode */
+
+   int         fetch_size;      /* fetch size for this remote table */
 } PgFdwRelationInfo;
 
 /* in postgres_fdw.c */
index 5c6ead1967dd1ae6263d3763706b69360734fcc5..ec8a30a3d9897be4cd7b1026d03b914039e052db 100644 (file)
@@ -919,4 +919,48 @@ BEGIN;
 DROP TYPE "Colors" CASCADE;
 IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5)
   FROM SERVER loopback INTO import_dest5;  -- ERROR
+
+ROLLBACK;
+
+BEGIN;
+
+
+CREATE SERVER fetch101 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( fetch_size '101' );
+
+SELECT count(*)
+FROM pg_foreign_server
+WHERE srvname = 'fetch101'
+AND srvoptions @> array['fetch_size=101'];
+
+ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' );
+
+SELECT count(*)
+FROM pg_foreign_server
+WHERE srvname = 'fetch101'
+AND srvoptions @> array['fetch_size=101'];
+
+SELECT count(*)
+FROM pg_foreign_server
+WHERE srvname = 'fetch101'
+AND srvoptions @> array['fetch_size=202'];
+
+CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' );
+
+SELECT COUNT(*)
+FROM pg_foreign_table
+WHERE ftrelid = 'table30000'::regclass
+AND ftoptions @> array['fetch_size=30000'];
+
+ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000');
+
+SELECT COUNT(*)
+FROM pg_foreign_table
+WHERE ftrelid = 'table30000'::regclass
+AND ftoptions @> array['fetch_size=30000'];
+
+SELECT COUNT(*)
+FROM pg_foreign_table
+WHERE ftrelid = 'table30000'::regclass
+AND ftoptions @> array['fetch_size=60000'];
+
 ROLLBACK;
index 5a829d537a69816a31c645442866aa33303ee1d6..a90983cb1e92a68665f8c58545eb595f5f2d8f45 100644 (file)
        be considered shippable to the remote server.
        This option can only be specified for foreign servers, not per-table.
       </para>
+
+      <para>
+       When using the <literal>extensions</literal> option, <emphasis>it is the
+       user's responsibility</> that the listed extensions exist and behave
+       identically on both the local and remote servers.  Otherwise, remote
+       queries may fail or behave unexpectedly.
+      </para>
      </listitem>
     </varlistentry>
 
-   </variablelist>
+    <varlistentry>
+     <term><literal>fetch_size</literal></term>
+     <listitem>
+      <para>
+       This option specifies the number of rows <filename>postgres_fdw</> 
+       should get in each fetch operation. It can be specified for a foreign
+       table or a foreign server. The option specified on a table overrides
+       an option specified for the server.
+       The default is <literal>100</>.
+      </para>
+     </listitem>
+    </varlistentry>
 
-   <para>
-    When using the <literal>extensions</literal> option, <emphasis>it is the
-    user's responsibility</> that the listed extensions exist and behave
-    identically on both the local and remote servers.  Otherwise, remote
-    queries may fail or behave unexpectedly.
-   </para>
+   </variablelist>
 
   </sect3>