mirror of
https://github.com/postgres/postgres.git
synced 2025-09-03 15:22:11 +03:00
Implement an API to let foreign-data wrappers actually be functional.
This commit provides the core code and documentation needed. A contrib module test case will follow shortly. Shigeru Hanada, Jan Urbanski, Heikki Linnakangas
This commit is contained in:
@@ -347,6 +347,7 @@ RelOptInfo - a relation or joined relations
|
||||
IndexPath - index scan
|
||||
BitmapHeapPath - top of a bitmapped index scan
|
||||
TidPath - scan by CTID
|
||||
ForeignPath - scan a foreign table
|
||||
AppendPath - append multiple subpaths together
|
||||
MergeAppendPath - merge multiple subpaths, preserving their common sort order
|
||||
ResultPath - a Result plan node (used for FROM-less SELECT)
|
||||
|
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "catalog/pg_class.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#ifdef OPTIMIZER_DEBUG
|
||||
#include "nodes/print.h"
|
||||
@@ -34,6 +35,7 @@
|
||||
#include "parser/parse_clause.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "rewrite/rewriteManip.h"
|
||||
#include "utils/lsyscache.h"
|
||||
|
||||
|
||||
/* These parameters are set by GUC */
|
||||
@@ -63,6 +65,8 @@ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte);
|
||||
static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte);
|
||||
static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte);
|
||||
static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist);
|
||||
static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery,
|
||||
bool *differentTypes);
|
||||
@@ -197,9 +201,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Plain relation */
|
||||
Assert(rel->rtekind == RTE_RELATION);
|
||||
set_plain_rel_pathlist(root, rel, rte);
|
||||
if (get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE)
|
||||
{
|
||||
/* Foreign table */
|
||||
set_foreign_pathlist(root, rel, rte);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Plain relation */
|
||||
set_plain_rel_pathlist(root, rel, rte);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef OPTIMIZER_DEBUG
|
||||
@@ -904,6 +916,23 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
set_cheapest(rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* set_foreign_pathlist
|
||||
* Build the (single) access path for a foreign table RTE
|
||||
*/
|
||||
static void
|
||||
set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
{
|
||||
/* Mark rel with estimated output rows, width, etc */
|
||||
set_foreign_size_estimates(root, rel);
|
||||
|
||||
/* Generate appropriate path */
|
||||
add_path(rel, (Path *) create_foreignscan_path(root, rel));
|
||||
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
set_cheapest(rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* make_rel_from_joinlist
|
||||
* Build access paths using a "joinlist" to guide the join path search.
|
||||
@@ -1503,6 +1532,9 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
||||
case T_TidPath:
|
||||
ptype = "TidScan";
|
||||
break;
|
||||
case T_ForeignPath:
|
||||
ptype = "ForeignScan";
|
||||
break;
|
||||
case T_AppendPath:
|
||||
ptype = "Append";
|
||||
break;
|
||||
|
@@ -3324,6 +3324,34 @@ set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan)
|
||||
set_baserel_size_estimates(root, rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* set_foreign_size_estimates
|
||||
* Set the size estimates for a base relation that is a foreign table.
|
||||
*
|
||||
* There is not a whole lot that we can do here; the foreign-data wrapper
|
||||
* is responsible for producing useful estimates. We can do a decent job
|
||||
* of estimating baserestrictcost, so we set that, and we also set up width
|
||||
* using what will be purely datatype-driven estimates from the targetlist.
|
||||
* There is no way to do anything sane with the rows value, so we just put
|
||||
* a default estimate and hope that the wrapper can improve on it. The
|
||||
* wrapper's PlanForeignScan function will be called momentarily.
|
||||
*
|
||||
* The rel's targetlist and restrictinfo list must have been constructed
|
||||
* already.
|
||||
*/
|
||||
void
|
||||
set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
||||
{
|
||||
/* Should only be applied to base relations */
|
||||
Assert(rel->relid > 0);
|
||||
|
||||
rel->rows = 1000; /* entirely bogus default estimate */
|
||||
|
||||
cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
|
||||
|
||||
set_rel_width(root, rel);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* set_rel_width
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "access/skey.h"
|
||||
#include "foreign/fdwapi.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
@@ -71,6 +72,8 @@ static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
|
||||
List *tlist, List *scan_clauses);
|
||||
static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
|
||||
List *tlist, List *scan_clauses);
|
||||
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
|
||||
List *tlist, List *scan_clauses);
|
||||
static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
|
||||
Plan *outer_plan, Plan *inner_plan);
|
||||
static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
|
||||
@@ -112,6 +115,8 @@ static CteScan *make_ctescan(List *qptlist, List *qpqual,
|
||||
Index scanrelid, int ctePlanId, int cteParam);
|
||||
static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
|
||||
Index scanrelid, int wtParam);
|
||||
static ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
|
||||
Index scanrelid, bool fsSystemCol, FdwPlan *fdwplan);
|
||||
static BitmapAnd *make_bitmap_and(List *bitmapplans);
|
||||
static BitmapOr *make_bitmap_or(List *bitmapplans);
|
||||
static NestLoop *make_nestloop(List *tlist,
|
||||
@@ -206,6 +211,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
|
||||
case T_ValuesScan:
|
||||
case T_CteScan:
|
||||
case T_WorkTableScan:
|
||||
case T_ForeignScan:
|
||||
plan = create_scan_plan(root, best_path);
|
||||
break;
|
||||
case T_HashJoin:
|
||||
@@ -346,6 +352,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
|
||||
scan_clauses);
|
||||
break;
|
||||
|
||||
case T_ForeignScan:
|
||||
plan = (Plan *) create_foreignscan_plan(root,
|
||||
(ForeignPath *) best_path,
|
||||
tlist,
|
||||
scan_clauses);
|
||||
break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized node type: %d",
|
||||
(int) best_path->pathtype);
|
||||
@@ -468,6 +481,7 @@ disuse_physical_tlist(Plan *plan, Path *path)
|
||||
case T_ValuesScan:
|
||||
case T_CteScan:
|
||||
case T_WorkTableScan:
|
||||
case T_ForeignScan:
|
||||
plan->targetlist = build_relation_tlist(path->parent);
|
||||
break;
|
||||
default:
|
||||
@@ -1755,6 +1769,56 @@ create_worktablescan_plan(PlannerInfo *root, Path *best_path,
|
||||
return scan_plan;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_foreignscan_plan
|
||||
* Returns a foreignscan plan for the base relation scanned by 'best_path'
|
||||
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
|
||||
*/
|
||||
static ForeignScan *
|
||||
create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
|
||||
List *tlist, List *scan_clauses)
|
||||
{
|
||||
ForeignScan *scan_plan;
|
||||
RelOptInfo *rel = best_path->path.parent;
|
||||
Index scan_relid = rel->relid;
|
||||
RangeTblEntry *rte;
|
||||
bool fsSystemCol;
|
||||
int i;
|
||||
|
||||
/* it should be a base rel... */
|
||||
Assert(scan_relid > 0);
|
||||
Assert(rel->rtekind == RTE_RELATION);
|
||||
rte = planner_rt_fetch(scan_relid, root);
|
||||
Assert(rte->rtekind == RTE_RELATION);
|
||||
|
||||
/* Sort clauses into best execution order */
|
||||
scan_clauses = order_qual_clauses(root, scan_clauses);
|
||||
|
||||
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
|
||||
scan_clauses = extract_actual_clauses(scan_clauses, false);
|
||||
|
||||
/* Detect whether any system columns are requested from rel */
|
||||
fsSystemCol = false;
|
||||
for (i = rel->min_attr; i < 0; i++)
|
||||
{
|
||||
if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
|
||||
{
|
||||
fsSystemCol = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
scan_plan = make_foreignscan(tlist,
|
||||
scan_clauses,
|
||||
scan_relid,
|
||||
fsSystemCol,
|
||||
best_path->fdwplan);
|
||||
|
||||
copy_path_costsize(&scan_plan->scan.plan, &best_path->path);
|
||||
|
||||
return scan_plan;
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
@@ -2978,6 +3042,28 @@ make_worktablescan(List *qptlist,
|
||||
return node;
|
||||
}
|
||||
|
||||
static ForeignScan *
|
||||
make_foreignscan(List *qptlist,
|
||||
List *qpqual,
|
||||
Index scanrelid,
|
||||
bool fsSystemCol,
|
||||
FdwPlan *fdwplan)
|
||||
{
|
||||
ForeignScan *node = makeNode(ForeignScan);
|
||||
Plan *plan = &node->scan.plan;
|
||||
|
||||
/* cost should be inserted by caller */
|
||||
plan->targetlist = qptlist;
|
||||
plan->qual = qpqual;
|
||||
plan->lefttree = NULL;
|
||||
plan->righttree = NULL;
|
||||
node->scan.scanrelid = scanrelid;
|
||||
node->fsSystemCol = fsSystemCol;
|
||||
node->fdwplan = fdwplan;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
Append *
|
||||
make_append(List *appendplans, List *tlist)
|
||||
{
|
||||
|
@@ -1914,7 +1914,8 @@ preprocess_rowmarks(PlannerInfo *root)
|
||||
newrc->rti = newrc->prti = i;
|
||||
newrc->rowmarkId = ++(root->glob->lastRowMarkId);
|
||||
/* real tables support REFERENCE, anything else needs COPY */
|
||||
if (rte->rtekind == RTE_RELATION)
|
||||
if (rte->rtekind == RTE_RELATION &&
|
||||
get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE)
|
||||
newrc->markType = ROW_MARK_REFERENCE;
|
||||
else
|
||||
newrc->markType = ROW_MARK_COPY;
|
||||
|
@@ -402,6 +402,18 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
|
||||
fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
|
||||
}
|
||||
break;
|
||||
case T_ForeignScan:
|
||||
{
|
||||
ForeignScan *splan = (ForeignScan *) plan;
|
||||
|
||||
splan->scan.scanrelid += rtoffset;
|
||||
splan->scan.plan.targetlist =
|
||||
fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset);
|
||||
splan->scan.plan.qual =
|
||||
fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_NestLoop:
|
||||
case T_MergeJoin:
|
||||
case T_HashJoin:
|
||||
|
@@ -2054,6 +2054,10 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
|
||||
context.paramids = bms_add_members(context.paramids, scan_params);
|
||||
break;
|
||||
|
||||
case T_ForeignScan:
|
||||
context.paramids = bms_add_members(context.paramids, scan_params);
|
||||
break;
|
||||
|
||||
case T_ModifyTable:
|
||||
{
|
||||
ModifyTable *mtplan = (ModifyTable *) plan;
|
||||
|
@@ -17,6 +17,7 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "catalog/pg_operator.h"
|
||||
#include "foreign/fdwapi.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "optimizer/clauses.h"
|
||||
@@ -1419,6 +1420,41 @@ create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel)
|
||||
return pathnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_foreignscan_path
|
||||
* Creates a path corresponding to a scan of a foreign table,
|
||||
* returning the pathnode.
|
||||
*/
|
||||
ForeignPath *
|
||||
create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel)
|
||||
{
|
||||
ForeignPath *pathnode = makeNode(ForeignPath);
|
||||
RangeTblEntry *rte;
|
||||
FdwRoutine *fdwroutine;
|
||||
FdwPlan *fdwplan;
|
||||
|
||||
pathnode->path.pathtype = T_ForeignScan;
|
||||
pathnode->path.parent = rel;
|
||||
pathnode->path.pathkeys = NIL; /* result is always unordered */
|
||||
|
||||
/* Get FDW's callback info */
|
||||
rte = planner_rt_fetch(rel->relid, root);
|
||||
fdwroutine = GetFdwRoutineByRelId(rte->relid);
|
||||
|
||||
/* Let the FDW do its planning */
|
||||
fdwplan = fdwroutine->PlanForeignScan(rte->relid, root, rel);
|
||||
if (fdwplan == NULL || !IsA(fdwplan, FdwPlan))
|
||||
elog(ERROR, "foreign-data wrapper PlanForeignScan function for relation %u did not return an FdwPlan struct",
|
||||
rte->relid);
|
||||
pathnode->fdwplan = fdwplan;
|
||||
|
||||
/* use costs estimated by FDW */
|
||||
pathnode->path.startup_cost = fdwplan->startup_cost;
|
||||
pathnode->path.total_cost = fdwplan->total_cost;
|
||||
|
||||
return pathnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_nestloop_path
|
||||
* Creates a pathnode corresponding to a nestloop join between two
|
||||
|
@@ -90,12 +90,6 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
||||
*/
|
||||
relation = heap_open(relationObjectId, NoLock);
|
||||
|
||||
/* Foreign table scans are not implemented yet. */
|
||||
if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("foreign table scans are not yet supported")));
|
||||
|
||||
rel->min_attr = FirstLowInvalidHeapAttributeNumber + 1;
|
||||
rel->max_attr = RelationGetNumberOfAttributes(relation);
|
||||
rel->reltablespace = RelationGetForm(relation)->reltablespace;
|
||||
@@ -463,6 +457,11 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
|
||||
*pages = 1;
|
||||
*tuples = 1;
|
||||
break;
|
||||
case RELKIND_FOREIGN_TABLE:
|
||||
/* Just use whatever's in pg_class */
|
||||
*pages = rel->rd_rel->relpages;
|
||||
*tuples = rel->rd_rel->reltuples;
|
||||
break;
|
||||
default:
|
||||
/* else it has no disk storage; probably shouldn't get here? */
|
||||
*pages = 0;
|
||||
|
Reference in New Issue
Block a user