the costs of a ForeignScan.
Also implement pgEstimateCost() for postgresql_fdw, which estimate
the costs as sum of:
* costs of sequencial scan
* connection cost (if connection_cost was set in the server generic
option)
* transfer cost (if transfer_cost, cost-per-width, was set in the
server generic option)
{
DefElem *def = lfirst(cell);
+ if (!is_libpq_connection_option(def->defname))
+ continue;
+
appendStringInfo(buf, "%s='%s' ", def->defname,
escape_param_str(strVal(def->arg)));
}
{
DefElem *def = lfirst(cell);
+ if (!is_libpq_connection_option(def->defname))
+ continue;
+
appendStringInfo(buf, "%s='%s' ", def->defname,
escape_param_str(strVal(def->arg)));
}
DefElem *def = lfirst(cell);
+ if (!is_libpq_connection_option(def->defname))
+ continue;
+
appendStringInfo(buf, "%s='%s' ", def->defname,
escape_param_str(strVal(def->arg)));
}
-- test foreign data wrapper functionality
CREATE USER dblink_regression_test;
CREATE FOREIGN DATA WRAPPER postgresql;
-CREATE SERVER fdtest FOREIGN DATA WRAPPER postgresql OPTIONS (dbname 'contrib_regression');
+CREATE SERVER fdtest FOREIGN DATA WRAPPER postgresql OPTIONS (dbname 'contrib_regression', connect_cost '100.0');
CREATE USER MAPPING FOR public SERVER fdtest;
GRANT USAGE ON FOREIGN SERVER fdtest TO dblink_regression_test;
GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO dblink_regression_test;
CREATE USER dblink_regression_test;
CREATE FOREIGN DATA WRAPPER postgresql;
-CREATE SERVER fdtest FOREIGN DATA WRAPPER postgresql OPTIONS (dbname 'contrib_regression');
+CREATE SERVER fdtest FOREIGN DATA WRAPPER postgresql OPTIONS (dbname 'contrib_regression', connect_cost '100.0');
CREATE USER MAPPING FOR public SERVER fdtest;
GRANT USAGE ON FOREIGN SERVER fdtest TO dblink_regression_test;
GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO dblink_regression_test;
-- create FOREIGN DATA WRAPPER for PostgresSQL
CREATE FOREIGN DATA WRAPPER contrib_regression_wrapper
HANDLER postgresql_fdw_handler
- VALIDATOR postgresql_fdw_validator;
+ VALIDATOR postgresql_fdw_validator;
-- create FOREIGN SERVER for remote database 1
CREATE SERVER contrib_regression_srv_1
FOREIGN DATA WRAPPER contrib_regression_wrapper
- OPTIONS (host 'localhost', dbname 'contrib_regression_f1');
+ OPTIONS (host 'localhost', dbname 'contrib_regression_f1',
+ connection_cost '100.0', transfer_cost '0.01');
CREATE USER MAPPING FOR PUBLIC SERVER contrib_regression_srv_1;
-- create FOREIGN SERVER for remote database 2
CREATE SERVER contrib_regression_srv_2
FOREIGN DATA WRAPPER contrib_regression_wrapper
- OPTIONS (host 'localhost', dbname 'contrib_regression_f2');
+ OPTIONS (host 'localhost', dbname 'contrib_regression_f2');
CREATE USER MAPPING FOR PUBLIC SERVER contrib_regression_srv_2;
-- Check ALTER FOREIGN TABLE OWNER TO before create various test tables
CREATE FOREIGN TABLE ft1 (c1 integer) SERVER contrib_regression_srv_2;
#include "libpq-fe.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
-#include "nodes/nodeFuncs.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/relation.h"
#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "parser/parsetree.h"
#include "parser/scansup.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
static void pgReOpen(ForeignScanState *scanstate);
static void pgAnalyze(Relation rel, VacuumStmt *vacstmt, bool update_reltuples,
bool inh);
+static void pgEstimateCost(Path *path, PlannerInfo *root, RelOptInfo *baserel);
/* deparse SQL from the request */
static bool is_immutable_func(Oid funcid);
-static bool is_foreign_qual(ExprState *state);
+static bool is_foreign_qual(Expr *expr);
static bool foreign_qual_walker(Node *node, void *context);
static char *deparseSql(ForeignScanState *scanstate);
static int flatten_deflist(List *options,
pgClose,
pgReOpen,
pgAnalyze,
+ pgEstimateCost,
};
/*
pgConnectServer(ForeignServer *server, UserMapping *user)
{
PGconn *conn;
+ const char **all_keywords;
+ const char **all_values;
const char **keywords;
const char **values;
int n;
+ int i, j;
/*
- * construct connection params from options of ForeignDataWrapper,
- * ForeignServer and UserMapping. Assuming all GENERIC OPTIONS are
- * conneciton information.
- *
- * TODO: allow non-connection options and ignore them during constructing
- * connection string.
+ * Construct connection params from generic options of ForeignServer and
+ * UserMapping. Generic options might not be a one of connection options.
*/
n = list_length(server->options) + list_length(user->options) + 1;
+ all_keywords = (const char **) palloc(sizeof(char *) * n);
+ all_values = (const char **) palloc(sizeof(char *) * n);
keywords = (const char **) palloc(sizeof(char *) * n);
values = (const char **) palloc(sizeof(char *) * n);
n = 0;
- n += flatten_deflist(server->options, keywords + n, values + n);
- n += flatten_deflist(user->options, keywords + n, values + n);
- keywords[n] = values[n] = NULL;
+ n += flatten_deflist(server->options, all_keywords + n, all_values + n);
+ n += flatten_deflist(user->options, all_keywords + n, all_values + n);
+ all_keywords[n] = all_values[n] = NULL;
+
+ for (i = 0, j = 0; all_keywords[i]; i++)
+ {
+ /* Use only libpq connection options. */
+ if (!is_libpq_connection_option(all_keywords[i]))
+ continue;
+ keywords[j] = all_keywords[i];
+ values[j] = all_values[i];
+ j++;
+ }
+ keywords[j] = values[j] = NULL;
+ pfree(all_keywords);
+ pfree(all_values);
/* verify connection parameters and do connect */
check_conn_params(keywords, values);
* - scalar array operator (ANY/ALL)
*/
static bool
-is_foreign_qual(ExprState *state)
+is_foreign_qual(Expr *expr)
{
- return !foreign_qual_walker((Node *) state->expr, NULL);
+ return !foreign_qual_walker((Node *) expr, NULL);
}
/*
{
ExprState *state = lfirst(lc);
- if (is_foreign_qual(state))
+ if (is_foreign_qual(state->expr))
{
elog(DEBUG1, "foreign qual: %s", nodeToString(state->expr));
foreign_qual = lappend(foreign_qual, state);
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("analyze of remote PostgreSQL table not implemented")));
}
+
+/*
+ * Estimate costs of scanning on a foreign table.
+ *
+ * baserel->baserestrictinfo can be used to examine quals on the relation.
+ */
+static void
+pgEstimateCost(Path *path, PlannerInfo *root, RelOptInfo *baserel)
+{
+ RangeTblEntry *rte;
+ ForeignTable *table;
+ ForeignServer *server;
+ int n;
+ const char **keywords;
+ const char **values;
+ int i;
+
+ /* Use the costs of sequential scan as approximate values. */
+ cost_seqscan(path, root, baserel);
+
+ /*
+ * Retrieve generic options from the target table and its server to correct
+ * costs.
+ */
+ rte = planner_rt_fetch(baserel->relid, root);
+ table = GetForeignTable(rte->relid);
+ server = GetForeignServer(table->serverid);
+ n = list_length(table->options) + list_length(server->options) + 1;
+ keywords = (const char **) palloc(sizeof(char *) * n);
+ values = (const char **) palloc(sizeof(char *) * n);
+ n = 0;
+ n += flatten_deflist(server->options, keywords + n, values + n);
+ n += flatten_deflist(table->options, keywords + n, values + n);
+ keywords[n] = values[n] = NULL;
+
+ /*
+ * Current costs in Path have been estimated as sequencial scan on a local
+ * table with cost_seqscan() above. If any cost factor option was set in a
+ * generic option of the foreign server or foreign table, we corrent the
+ * costs with the factor. Because currently the postgresql_fdw retrieve
+ * all result tuples at a time, first call of Iterate(), both startup_cost
+ * and total_cost will be corrected.
+ */
+ for (i = 0; keywords[i]; i++)
+ {
+ if (pg_strcasecmp(keywords[i], "connection_cost") == 0)
+ {
+ double cost;
+ cost = strtod(values[i], NULL);
+ path->startup_cost += cost;
+ path->total_cost += cost;
+ }
+ else if (pg_strcasecmp(keywords[i], "transfer_cost") == 0)
+ {
+ double cost;
+ cost = strtod(values[i], NULL);
+ cost *= path->parent->width * path->parent->rows;
+ path->startup_cost += cost;
+ path->total_cost += cost;
+ }
+ }
+
+ pfree(keywords);
+ pfree(values);
+}
-- create FOREIGN DATA WRAPPER for PostgresSQL
CREATE FOREIGN DATA WRAPPER contrib_regression_wrapper
HANDLER postgresql_fdw_handler
- VALIDATOR postgresql_fdw_validator;
+ VALIDATOR postgresql_fdw_validator;
-- create FOREIGN SERVER for remote database 1
CREATE SERVER contrib_regression_srv_1
FOREIGN DATA WRAPPER contrib_regression_wrapper
- OPTIONS (host 'localhost', dbname 'contrib_regression_f1');
+ OPTIONS (host 'localhost', dbname 'contrib_regression_f1',
+ connection_cost '100.0', transfer_cost '0.01');
CREATE USER MAPPING FOR PUBLIC SERVER contrib_regression_srv_1;
-- create FOREIGN SERVER for remote database 2
CREATE SERVER contrib_regression_srv_2
FOREIGN DATA WRAPPER contrib_regression_wrapper
- OPTIONS (host 'localhost', dbname 'contrib_regression_f2');
+ OPTIONS (host 'localhost', dbname 'contrib_regression_f2');
CREATE USER MAPPING FOR PUBLIC SERVER contrib_regression_srv_2;
-- Check ALTER FOREIGN TABLE OWNER TO before create various test tables
{
const char *optname;
Oid optcontext; /* Oid of catalog in which option may appear */
+ bool is_conn_opt; /* True if the option is a connection option */
};
/*
*/
static struct PgFdwOption valid_options[] = {
/* Connection Options */
- {"authtype", ForeignServerRelationId},
- {"service", ForeignServerRelationId},
- {"user", UserMappingRelationId},
- {"password", UserMappingRelationId},
- {"connect_timeout", ForeignServerRelationId},
- {"dbname", ForeignServerRelationId},
- {"host", ForeignServerRelationId},
- {"hostaddr", ForeignServerRelationId},
- {"port", ForeignServerRelationId},
- {"tty", ForeignServerRelationId},
- {"options", ForeignServerRelationId},
- {"requiressl", ForeignServerRelationId},
- {"sslmode", ForeignServerRelationId},
- {"gsslib", ForeignServerRelationId},
-
- /* Other options */
- {"nspname", ForeignTableRelationId},
- {"relname", ForeignTableRelationId},
+ {"authtype", ForeignServerRelationId, true},
+ {"service", ForeignServerRelationId, true},
+ {"user", UserMappingRelationId, true},
+ {"password", UserMappingRelationId, true},
+ {"connect_timeout", ForeignServerRelationId, true},
+ {"dbname", ForeignServerRelationId, true},
+ {"host", ForeignServerRelationId, true},
+ {"hostaddr", ForeignServerRelationId, true},
+ {"port", ForeignServerRelationId, true},
+ {"tty", ForeignServerRelationId, true},
+ {"options", ForeignServerRelationId, true},
+ {"requiressl", ForeignServerRelationId, true},
+ {"sslmode", ForeignServerRelationId, true},
+ {"gsslib", ForeignServerRelationId, true},
+
+ /* Catalog options */
+ {"nspname", ForeignTableRelationId, false},
+ {"relname", ForeignTableRelationId, false},
+
+ /* Planner cost options */
+ {"connection_cost", ForeignServerRelationId, false},
+ {"transfer_cost", ForeignServerRelationId, false},
/* Centinel */
- {NULL, InvalidOid}
+ {NULL, InvalidOid, false}
};
-
/*
- * Check if the provided option is one of libpq conninfo options.
+ * Check if the provided option is one of valid options.
* context is the Oid of the catalog the option came from, or 0 if we
* don't care.
*/
return false;
}
+/*
+ * Check if the provided option is one of libpq conninfo options.
+ * XXX: Should be moved to interface/libpq or backend/libpq?
+ */
+bool
+is_libpq_connection_option(const char *option)
+{
+ struct PgFdwOption *opt;
+
+ for (opt = valid_options; opt->optname; opt++)
+ if (opt->is_conn_opt && strcmp(opt->optname, option) == 0)
+ return true;
+ return false;
+}
/*
- * Validate the generic option given to SERVER or USER MAPPING.
+ * Validate the generic option given to FOREIGN DATA WRAPPER, SERVER, USER
+ * MAPPING or FOREIGN TABLE.
* Raise an ERROR if the option or its value is considered
* invalid.
*
cost_foreignscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel)
{
+ RangeTblEntry *rte;
+ ForeignTable *table;
+ ForeignServer *server;
+ ForeignDataWrapper *wrapper;
+ FdwRoutine *routine;
+
/* Should only be applied to base relations */
Assert(baserel->relid > 0);
Assert(baserel->rtekind == RTE_RELATION);
- /* XXX estimate cost to scan the foreign table */
- path->startup_cost = 10000.0;
- path->total_cost = 10000.0;
+ /* Have the wrapper handler estimate the cost of this scan. */
+ rte = planner_rt_fetch(baserel->relid, root);
+ table = GetForeignTable(rte->relid);
+ server = GetForeignServer(table->serverid);
+ wrapper = GetForeignDataWrapper(server->fdwid);
+ routine = GetFdwRoutine(wrapper->fdwhandler);
+
+ /* Use cost_seqscan() instead if the wrapper did't support this API. */
+ if (routine->EstimateCost != NULL)
+ routine->EstimateCost(path, root, baserel);
+ else
+ cost_seqscan(path, root, baserel);
}
/*
*rescan_total_cost = run_cost;
}
break;
- case T_ForeignScan:
- /* XXX estimate cost to scan foreign table via FDW */
- *rescan_startup_cost = path->startup_cost;
- *rescan_total_cost = path->total_cost;
- break;
default:
*rescan_startup_cost = path->startup_cost;
*rescan_total_cost = path->total_cost;
scan_relid);
copy_path_costsize(&scan_plan->scan.plan, best_path);
- /* XXX override estimated rows to emulate big-result */
- scan_plan->scan.plan.plan_rows = 1000;
return scan_plan;
}
*pages = 1;
*tuples = 1;
break;
- case RELKIND_FOREIGN_TABLE:
- /* XXX: estimate with FDW or use pg_class.reltuples/relpages ? */
- break;
default:
/* else it has no disk storage; probably shouldn't get here? */
*pages = 0;
typedef struct FSConnection FSConnection;
typedef struct FdwReply FdwReply;
+/* catalog manipulation */
extern ForeignServer *GetForeignServer(Oid serverid);
extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
extern Oid GetForeignServerOidByName(const char *name, bool missing_ok);
/* ALTER FOREIGN TABLE ... OPTIONS (...) handlers */
extern void ATExecGenericOptions(Relation rel, List *options);
+extern bool is_libpq_connection_option(const char *option);
/* foreign server connections */
extern FSConnection *ConnectToForeignServer(FdwRoutine *routine,
/*
* Common interface routines of FDW baed on SQL/MED standard.
- * Each FDW must implement these routines.
+ * A foreign-data wrapper must implement these routines.
*/
struct FdwRoutine
{
* foreign table. Statistics to be updated are:
* - all columns of pg_statistics
* - reltuples, relpages of pg_class (only if update_reltuples was true)
+ *
* If this API was not implemented (== NULL), ANALYZE skips that relation.
*/
void (*Analyze)(Relation rel, VacuumStmt *vacstmt, bool update_reltuples,
bool inh);
+
+ /*
+ * Estimate costs of scanning on the foreign table.
+ * FDW should update startup_cost and total_cost in the Path.
+ *
+ * If this API was not implemented (== NULL), cost_seqscan() will be used
+ * instead.
+ */
+ void (*EstimateCost)(Path *path, PlannerInfo *root, RelOptInfo *baserel);
};
/* ----------------------------------------------------------------
CREATE SERVER s7 TYPE 'oracle' VERSION '17.0' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b');
CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (foo '1'); -- ERROR
ERROR: invalid option "foo"
-HINT: Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib
+HINT: Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib, connection_cost, transfer_cost
CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (host 'localhost', dbname 's8db');
\des+
List of foreign servers