mirror of
https://github.com/postgres/postgres.git
synced 2025-06-13 07:41:39 +03:00
Redo postgres_fdw's planner code so it can handle parameterized paths.
I wasn't going to ship this without having at least some example of how to do that. This version isn't terribly bright; in particular it won't consider any combinations of multiple join clauses. Given the cost of executing a remote EXPLAIN, I'm not sure we want to be very aggressive about doing that, anyway. In support of this, refactor generate_implied_equalities_for_indexcol so that it can be used to extract equivalence clauses that aren't necessarily tied to an index.
This commit is contained in:
@ -58,11 +58,8 @@
|
|||||||
*/
|
*/
|
||||||
typedef struct foreign_glob_cxt
|
typedef struct foreign_glob_cxt
|
||||||
{
|
{
|
||||||
/* Input values */
|
PlannerInfo *root; /* global planner state */
|
||||||
PlannerInfo *root;
|
RelOptInfo *foreignrel; /* the foreign relation we are planning for */
|
||||||
RelOptInfo *foreignrel;
|
|
||||||
/* Result values */
|
|
||||||
List *param_numbers; /* Param IDs of PARAM_EXTERN Params */
|
|
||||||
} foreign_glob_cxt;
|
} foreign_glob_cxt;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -82,12 +79,21 @@ typedef struct foreign_loc_cxt
|
|||||||
FDWCollateState state; /* state of current collation choice */
|
FDWCollateState state; /* state of current collation choice */
|
||||||
} foreign_loc_cxt;
|
} foreign_loc_cxt;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Context for deparseExpr
|
||||||
|
*/
|
||||||
|
typedef struct deparse_expr_cxt
|
||||||
|
{
|
||||||
|
PlannerInfo *root; /* global planner state */
|
||||||
|
RelOptInfo *foreignrel; /* the foreign relation we are planning for */
|
||||||
|
StringInfo buf; /* output buffer to append to */
|
||||||
|
List **params_list; /* exprs that will become remote Params */
|
||||||
|
} deparse_expr_cxt;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Functions to determine whether an expression can be evaluated safely on
|
* Functions to determine whether an expression can be evaluated safely on
|
||||||
* remote server.
|
* remote server.
|
||||||
*/
|
*/
|
||||||
static bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel,
|
|
||||||
Expr *expr, List **param_numbers);
|
|
||||||
static bool foreign_expr_walker(Node *node,
|
static bool foreign_expr_walker(Node *node,
|
||||||
foreign_glob_cxt *glob_cxt,
|
foreign_glob_cxt *glob_cxt,
|
||||||
foreign_loc_cxt *outer_cxt);
|
foreign_loc_cxt *outer_cxt);
|
||||||
@ -108,76 +114,46 @@ static void deparseColumnRef(StringInfo buf, int varno, int varattno,
|
|||||||
PlannerInfo *root);
|
PlannerInfo *root);
|
||||||
static void deparseRelation(StringInfo buf, Relation rel);
|
static void deparseRelation(StringInfo buf, Relation rel);
|
||||||
static void deparseStringLiteral(StringInfo buf, const char *val);
|
static void deparseStringLiteral(StringInfo buf, const char *val);
|
||||||
static void deparseExpr(StringInfo buf, Expr *expr, PlannerInfo *root);
|
static void deparseExpr(Expr *expr, deparse_expr_cxt *context);
|
||||||
static void deparseVar(StringInfo buf, Var *node, PlannerInfo *root);
|
static void deparseVar(Var *node, deparse_expr_cxt *context);
|
||||||
static void deparseConst(StringInfo buf, Const *node, PlannerInfo *root);
|
static void deparseConst(Const *node, deparse_expr_cxt *context);
|
||||||
static void deparseParam(StringInfo buf, Param *node, PlannerInfo *root);
|
static void deparseParam(Param *node, deparse_expr_cxt *context);
|
||||||
static void deparseArrayRef(StringInfo buf, ArrayRef *node, PlannerInfo *root);
|
static void deparseArrayRef(ArrayRef *node, deparse_expr_cxt *context);
|
||||||
static void deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root);
|
static void deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context);
|
||||||
static void deparseOpExpr(StringInfo buf, OpExpr *node, PlannerInfo *root);
|
static void deparseOpExpr(OpExpr *node, deparse_expr_cxt *context);
|
||||||
static void deparseOperatorName(StringInfo buf, Form_pg_operator opform);
|
static void deparseOperatorName(StringInfo buf, Form_pg_operator opform);
|
||||||
static void deparseDistinctExpr(StringInfo buf, DistinctExpr *node,
|
static void deparseDistinctExpr(DistinctExpr *node, deparse_expr_cxt *context);
|
||||||
PlannerInfo *root);
|
static void deparseScalarArrayOpExpr(ScalarArrayOpExpr *node,
|
||||||
static void deparseScalarArrayOpExpr(StringInfo buf, ScalarArrayOpExpr *node,
|
deparse_expr_cxt *context);
|
||||||
PlannerInfo *root);
|
static void deparseRelabelType(RelabelType *node, deparse_expr_cxt *context);
|
||||||
static void deparseRelabelType(StringInfo buf, RelabelType *node,
|
static void deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context);
|
||||||
PlannerInfo *root);
|
static void deparseNullTest(NullTest *node, deparse_expr_cxt *context);
|
||||||
static void deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root);
|
static void deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context);
|
||||||
static void deparseNullTest(StringInfo buf, NullTest *node, PlannerInfo *root);
|
|
||||||
static void deparseArrayExpr(StringInfo buf, ArrayExpr *node,
|
|
||||||
PlannerInfo *root);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Examine each restriction clause in baserel's baserestrictinfo list,
|
* Examine each restriction clause in baserel's baserestrictinfo list,
|
||||||
* and classify them into three groups, which are returned as three lists:
|
* and classify them into two groups, which are returned as two lists:
|
||||||
* - remote_conds contains expressions that can be evaluated remotely,
|
* - remote_conds contains expressions that can be evaluated remotely
|
||||||
* and contain no PARAM_EXTERN Params
|
* - local_conds contains expressions that can't be evaluated remotely
|
||||||
* - param_conds contains expressions that can be evaluated remotely,
|
|
||||||
* but contain one or more PARAM_EXTERN Params
|
|
||||||
* - local_conds contains all expressions that can't be evaluated remotely
|
|
||||||
*
|
|
||||||
* In addition, the fourth output parameter param_numbers receives an integer
|
|
||||||
* list of the param IDs of the PARAM_EXTERN Params used in param_conds.
|
|
||||||
*
|
|
||||||
* The reason for segregating param_conds is mainly that it's difficult to
|
|
||||||
* use such conditions in remote EXPLAIN. We could do it, but unless the
|
|
||||||
* planner has been given representative values for all the Params, we'd
|
|
||||||
* have to guess at representative values to use in EXPLAIN EXECUTE.
|
|
||||||
* So for now we don't include them when doing remote EXPLAIN.
|
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
classifyConditions(PlannerInfo *root,
|
classifyConditions(PlannerInfo *root,
|
||||||
RelOptInfo *baserel,
|
RelOptInfo *baserel,
|
||||||
List **remote_conds,
|
List **remote_conds,
|
||||||
List **param_conds,
|
List **local_conds)
|
||||||
List **local_conds,
|
|
||||||
List **param_numbers)
|
|
||||||
{
|
{
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
|
|
||||||
*remote_conds = NIL;
|
*remote_conds = NIL;
|
||||||
*param_conds = NIL;
|
|
||||||
*local_conds = NIL;
|
*local_conds = NIL;
|
||||||
*param_numbers = NIL;
|
|
||||||
|
|
||||||
foreach(lc, baserel->baserestrictinfo)
|
foreach(lc, baserel->baserestrictinfo)
|
||||||
{
|
{
|
||||||
RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
|
RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
|
||||||
List *cur_param_numbers;
|
|
||||||
|
|
||||||
if (is_foreign_expr(root, baserel, ri->clause, &cur_param_numbers))
|
if (is_foreign_expr(root, baserel, ri->clause))
|
||||||
{
|
*remote_conds = lappend(*remote_conds, ri);
|
||||||
if (cur_param_numbers == NIL)
|
|
||||||
*remote_conds = lappend(*remote_conds, ri);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*param_conds = lappend(*param_conds, ri);
|
|
||||||
/* Use list_concat_unique_int to get rid of duplicates */
|
|
||||||
*param_numbers = list_concat_unique_int(*param_numbers,
|
|
||||||
cur_param_numbers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
*local_conds = lappend(*local_conds, ri);
|
*local_conds = lappend(*local_conds, ri);
|
||||||
}
|
}
|
||||||
@ -185,28 +161,21 @@ classifyConditions(PlannerInfo *root,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns true if given expr is safe to evaluate on the foreign server.
|
* Returns true if given expr is safe to evaluate on the foreign server.
|
||||||
*
|
|
||||||
* If result is true, we also return a list of param IDs of PARAM_EXTERN
|
|
||||||
* Params appearing in the expr into *param_numbers.
|
|
||||||
*/
|
*/
|
||||||
static bool
|
bool
|
||||||
is_foreign_expr(PlannerInfo *root,
|
is_foreign_expr(PlannerInfo *root,
|
||||||
RelOptInfo *baserel,
|
RelOptInfo *baserel,
|
||||||
Expr *expr,
|
Expr *expr)
|
||||||
List **param_numbers)
|
|
||||||
{
|
{
|
||||||
foreign_glob_cxt glob_cxt;
|
foreign_glob_cxt glob_cxt;
|
||||||
foreign_loc_cxt loc_cxt;
|
foreign_loc_cxt loc_cxt;
|
||||||
|
|
||||||
*param_numbers = NIL; /* default result */
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check that the expression consists of nodes that are safe to execute
|
* Check that the expression consists of nodes that are safe to execute
|
||||||
* remotely.
|
* remotely.
|
||||||
*/
|
*/
|
||||||
glob_cxt.root = root;
|
glob_cxt.root = root;
|
||||||
glob_cxt.foreignrel = baserel;
|
glob_cxt.foreignrel = baserel;
|
||||||
glob_cxt.param_numbers = NIL;
|
|
||||||
loc_cxt.collation = InvalidOid;
|
loc_cxt.collation = InvalidOid;
|
||||||
loc_cxt.state = FDW_COLLATE_NONE;
|
loc_cxt.state = FDW_COLLATE_NONE;
|
||||||
if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt))
|
if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt))
|
||||||
@ -226,18 +195,14 @@ is_foreign_expr(PlannerInfo *root,
|
|||||||
if (contain_mutable_functions((Node *) expr))
|
if (contain_mutable_functions((Node *) expr))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/*
|
/* OK to evaluate on the remote server */
|
||||||
* OK, so return list of param IDs too.
|
|
||||||
*/
|
|
||||||
*param_numbers = glob_cxt.param_numbers;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if expression is safe to execute remotely, and return true if so.
|
* Check if expression is safe to execute remotely, and return true if so.
|
||||||
*
|
*
|
||||||
* In addition, glob_cxt->param_numbers and *outer_cxt are updated.
|
* In addition, *outer_cxt is updated with collation information.
|
||||||
*
|
*
|
||||||
* We must check that the expression contains only node types we can deparse,
|
* We must check that the expression contains only node types we can deparse,
|
||||||
* that all types/functions/operators are safe to send (which we approximate
|
* that all types/functions/operators are safe to send (which we approximate
|
||||||
@ -271,19 +236,30 @@ foreign_expr_walker(Node *node,
|
|||||||
Var *var = (Var *) node;
|
Var *var = (Var *) node;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Var can be used if it is in the foreign table (we shouldn't
|
* If the Var is from the foreign table, we consider its
|
||||||
* really see anything else in baserestrict clauses, but let's
|
* collation (if any) safe to use. If it is from another
|
||||||
* check anyway).
|
* table, we treat its collation the same way as we would a
|
||||||
|
* Param's collation, ie it's not safe for it to have a
|
||||||
|
* non-default collation.
|
||||||
*/
|
*/
|
||||||
if (var->varno != glob_cxt->foreignrel->relid ||
|
if (var->varno == glob_cxt->foreignrel->relid &&
|
||||||
var->varlevelsup != 0)
|
var->varlevelsup == 0)
|
||||||
return false;
|
{
|
||||||
|
/* Var belongs to foreign table */
|
||||||
|
collation = var->varcollid;
|
||||||
|
state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Var belongs to some other table */
|
||||||
|
if (var->varcollid != InvalidOid &&
|
||||||
|
var->varcollid != DEFAULT_COLLATION_OID)
|
||||||
|
return false;
|
||||||
|
|
||||||
/*
|
/* We can consider that it doesn't set collation */
|
||||||
* If Var has a collation, consider that safe to use.
|
collation = InvalidOid;
|
||||||
*/
|
state = FDW_COLLATE_NONE;
|
||||||
collation = var->varcollid;
|
}
|
||||||
state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case T_Const:
|
case T_Const:
|
||||||
@ -308,30 +284,15 @@ foreign_expr_walker(Node *node,
|
|||||||
{
|
{
|
||||||
Param *p = (Param *) node;
|
Param *p = (Param *) node;
|
||||||
|
|
||||||
/*
|
|
||||||
* Only external parameters can be sent to remote. (XXX This
|
|
||||||
* needs to be improved, but at the point where this code
|
|
||||||
* runs, we should only see PARAM_EXTERN Params anyway.)
|
|
||||||
*/
|
|
||||||
if (p->paramkind != PARAM_EXTERN)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Collation handling is same as for Consts.
|
* Collation handling is same as for Consts.
|
||||||
*/
|
*/
|
||||||
if (p->paramcollid != InvalidOid &&
|
if (p->paramcollid != InvalidOid &&
|
||||||
p->paramcollid != DEFAULT_COLLATION_OID)
|
p->paramcollid != DEFAULT_COLLATION_OID)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
collation = InvalidOid;
|
collation = InvalidOid;
|
||||||
state = FDW_COLLATE_NONE;
|
state = FDW_COLLATE_NONE;
|
||||||
|
|
||||||
/*
|
|
||||||
* Report IDs of PARAM_EXTERN Params. We don't bother to
|
|
||||||
* eliminate duplicate list elements here; classifyConditions
|
|
||||||
* will do that.
|
|
||||||
*/
|
|
||||||
glob_cxt->param_numbers = lappend_int(glob_cxt->param_numbers,
|
|
||||||
p->paramid);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case T_ArrayRef:
|
case T_ArrayRef:
|
||||||
@ -791,17 +752,38 @@ deparseTargetList(StringInfo buf,
|
|||||||
/*
|
/*
|
||||||
* Deparse WHERE clauses in given list of RestrictInfos and append them to buf.
|
* Deparse WHERE clauses in given list of RestrictInfos and append them to buf.
|
||||||
*
|
*
|
||||||
|
* baserel is the foreign table we're planning for.
|
||||||
|
*
|
||||||
* If no WHERE clause already exists in the buffer, is_first should be true.
|
* If no WHERE clause already exists in the buffer, is_first should be true.
|
||||||
|
*
|
||||||
|
* If params is not NULL, it receives a list of Params and other-relation Vars
|
||||||
|
* used in the clauses; these values must be transmitted to the remote server
|
||||||
|
* as parameter values.
|
||||||
|
*
|
||||||
|
* If params is NULL, we're generating the query for EXPLAIN purposes,
|
||||||
|
* so Params and other-relation Vars should be replaced by dummy values.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
appendWhereClause(StringInfo buf,
|
appendWhereClause(StringInfo buf,
|
||||||
PlannerInfo *root,
|
PlannerInfo *root,
|
||||||
|
RelOptInfo *baserel,
|
||||||
List *exprs,
|
List *exprs,
|
||||||
bool is_first)
|
bool is_first,
|
||||||
|
List **params)
|
||||||
{
|
{
|
||||||
|
deparse_expr_cxt context;
|
||||||
int nestlevel;
|
int nestlevel;
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
|
|
||||||
|
if (params)
|
||||||
|
*params = NIL; /* initialize result list to empty */
|
||||||
|
|
||||||
|
/* Set up context struct for recursion */
|
||||||
|
context.root = root;
|
||||||
|
context.foreignrel = baserel;
|
||||||
|
context.buf = buf;
|
||||||
|
context.params_list = params;
|
||||||
|
|
||||||
/* Make sure any constants in the exprs are printed portably */
|
/* Make sure any constants in the exprs are printed portably */
|
||||||
nestlevel = set_transmission_modes();
|
nestlevel = set_transmission_modes();
|
||||||
|
|
||||||
@ -816,7 +798,7 @@ appendWhereClause(StringInfo buf,
|
|||||||
appendStringInfoString(buf, " AND ");
|
appendStringInfoString(buf, " AND ");
|
||||||
|
|
||||||
appendStringInfoChar(buf, '(');
|
appendStringInfoChar(buf, '(');
|
||||||
deparseExpr(buf, ri->clause, root);
|
deparseExpr(ri->clause, &context);
|
||||||
appendStringInfoChar(buf, ')');
|
appendStringInfoChar(buf, ')');
|
||||||
|
|
||||||
is_first = false;
|
is_first = false;
|
||||||
@ -1145,7 +1127,7 @@ deparseStringLiteral(StringInfo buf, const char *val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deparse given expression into buf.
|
* Deparse given expression into context->buf.
|
||||||
*
|
*
|
||||||
* This function must support all the same node types that foreign_expr_walker
|
* This function must support all the same node types that foreign_expr_walker
|
||||||
* accepts.
|
* accepts.
|
||||||
@ -1155,7 +1137,7 @@ deparseStringLiteral(StringInfo buf, const char *val)
|
|||||||
* should be self-parenthesized.
|
* should be self-parenthesized.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseExpr(StringInfo buf, Expr *node, PlannerInfo *root)
|
deparseExpr(Expr *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
if (node == NULL)
|
if (node == NULL)
|
||||||
return;
|
return;
|
||||||
@ -1163,40 +1145,40 @@ deparseExpr(StringInfo buf, Expr *node, PlannerInfo *root)
|
|||||||
switch (nodeTag(node))
|
switch (nodeTag(node))
|
||||||
{
|
{
|
||||||
case T_Var:
|
case T_Var:
|
||||||
deparseVar(buf, (Var *) node, root);
|
deparseVar((Var *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_Const:
|
case T_Const:
|
||||||
deparseConst(buf, (Const *) node, root);
|
deparseConst((Const *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_Param:
|
case T_Param:
|
||||||
deparseParam(buf, (Param *) node, root);
|
deparseParam((Param *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_ArrayRef:
|
case T_ArrayRef:
|
||||||
deparseArrayRef(buf, (ArrayRef *) node, root);
|
deparseArrayRef((ArrayRef *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_FuncExpr:
|
case T_FuncExpr:
|
||||||
deparseFuncExpr(buf, (FuncExpr *) node, root);
|
deparseFuncExpr((FuncExpr *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_OpExpr:
|
case T_OpExpr:
|
||||||
deparseOpExpr(buf, (OpExpr *) node, root);
|
deparseOpExpr((OpExpr *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_DistinctExpr:
|
case T_DistinctExpr:
|
||||||
deparseDistinctExpr(buf, (DistinctExpr *) node, root);
|
deparseDistinctExpr((DistinctExpr *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_ScalarArrayOpExpr:
|
case T_ScalarArrayOpExpr:
|
||||||
deparseScalarArrayOpExpr(buf, (ScalarArrayOpExpr *) node, root);
|
deparseScalarArrayOpExpr((ScalarArrayOpExpr *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_RelabelType:
|
case T_RelabelType:
|
||||||
deparseRelabelType(buf, (RelabelType *) node, root);
|
deparseRelabelType((RelabelType *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_BoolExpr:
|
case T_BoolExpr:
|
||||||
deparseBoolExpr(buf, (BoolExpr *) node, root);
|
deparseBoolExpr((BoolExpr *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_NullTest:
|
case T_NullTest:
|
||||||
deparseNullTest(buf, (NullTest *) node, root);
|
deparseNullTest((NullTest *) node, context);
|
||||||
break;
|
break;
|
||||||
case T_ArrayExpr:
|
case T_ArrayExpr:
|
||||||
deparseArrayExpr(buf, (ArrayExpr *) node, root);
|
deparseArrayExpr((ArrayExpr *) node, context);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unsupported expression type for deparse: %d",
|
elog(ERROR, "unsupported expression type for deparse: %d",
|
||||||
@ -1206,23 +1188,69 @@ deparseExpr(StringInfo buf, Expr *node, PlannerInfo *root)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deparse given Var node into buf.
|
* Deparse given Var node into context->buf.
|
||||||
|
*
|
||||||
|
* If the Var belongs to the foreign relation, just print its remote name.
|
||||||
|
* Otherwise, it's effectively a Param (and will in fact be a Param at
|
||||||
|
* run time). Handle it the same way we handle plain Params --- see
|
||||||
|
* deparseParam for comments.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseVar(StringInfo buf, Var *node, PlannerInfo *root)
|
deparseVar(Var *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
Assert(node->varlevelsup == 0);
|
StringInfo buf = context->buf;
|
||||||
deparseColumnRef(buf, node->varno, node->varattno, root);
|
|
||||||
|
if (node->varno == context->foreignrel->relid &&
|
||||||
|
node->varlevelsup == 0)
|
||||||
|
{
|
||||||
|
/* Var belongs to foreign table */
|
||||||
|
deparseColumnRef(buf, node->varno, node->varattno, context->root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Treat like a Param */
|
||||||
|
if (context->params_list)
|
||||||
|
{
|
||||||
|
int pindex = 0;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
/* find its index in params_list */
|
||||||
|
foreach(lc, *context->params_list)
|
||||||
|
{
|
||||||
|
pindex++;
|
||||||
|
if (equal(node, (Node *) lfirst(lc)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (lc == NULL)
|
||||||
|
{
|
||||||
|
/* not in list, so add it */
|
||||||
|
pindex++;
|
||||||
|
*context->params_list = lappend(*context->params_list, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(buf, "$%d", pindex);
|
||||||
|
appendStringInfo(buf, "::%s",
|
||||||
|
format_type_with_typemod(node->vartype,
|
||||||
|
node->vartypmod));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "(SELECT null::%s)",
|
||||||
|
format_type_with_typemod(node->vartype,
|
||||||
|
node->vartypmod));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deparse given constant value into buf.
|
* Deparse given constant value into context->buf.
|
||||||
*
|
*
|
||||||
* This function has to be kept in sync with ruleutils.c's get_const_expr.
|
* This function has to be kept in sync with ruleutils.c's get_const_expr.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseConst(StringInfo buf, Const *node, PlannerInfo *root)
|
deparseConst(Const *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
|
StringInfo buf = context->buf;
|
||||||
Oid typoutput;
|
Oid typoutput;
|
||||||
bool typIsVarlena;
|
bool typIsVarlena;
|
||||||
char *extval;
|
char *extval;
|
||||||
@ -1312,11 +1340,19 @@ deparseConst(StringInfo buf, Const *node, PlannerInfo *root)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deparse given Param node into buf.
|
* Deparse given Param node.
|
||||||
*
|
*
|
||||||
* We don't need to renumber the parameter ID, because the executor functions
|
* If we're generating the query "for real", add the Param to
|
||||||
* in postgres_fdw.c preserve the numbering of PARAM_EXTERN Params.
|
* context->params_list if it's not already present, and then use its index
|
||||||
* (This might change soon.)
|
* in that list as the remote parameter number.
|
||||||
|
*
|
||||||
|
* If we're just generating the query for EXPLAIN, replace the Param with
|
||||||
|
* a dummy expression "(SELECT null::<type>)". In all extant versions of
|
||||||
|
* Postgres, the planner will see that as an unknown constant value, which is
|
||||||
|
* what we want. (If we sent a Param, recent versions might try to use the
|
||||||
|
* value supplied for the Param as an estimated or even constant value, which
|
||||||
|
* we don't want.) This might need adjustment if we ever make the planner
|
||||||
|
* flatten scalar subqueries.
|
||||||
*
|
*
|
||||||
* Note: we label the Param's type explicitly rather than relying on
|
* Note: we label the Param's type explicitly rather than relying on
|
||||||
* transmitting a numeric type OID in PQexecParams(). This allows us to
|
* transmitting a numeric type OID in PQexecParams(). This allows us to
|
||||||
@ -1324,21 +1360,49 @@ deparseConst(StringInfo buf, Const *node, PlannerInfo *root)
|
|||||||
* do locally --- they need only have the same names.
|
* do locally --- they need only have the same names.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseParam(StringInfo buf, Param *node, PlannerInfo *root)
|
deparseParam(Param *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
Assert(node->paramkind == PARAM_EXTERN);
|
StringInfo buf = context->buf;
|
||||||
appendStringInfo(buf, "$%d", node->paramid);
|
|
||||||
appendStringInfo(buf, "::%s",
|
if (context->params_list)
|
||||||
format_type_with_typemod(node->paramtype,
|
{
|
||||||
node->paramtypmod));
|
int pindex = 0;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
/* find its index in params_list */
|
||||||
|
foreach(lc, *context->params_list)
|
||||||
|
{
|
||||||
|
pindex++;
|
||||||
|
if (equal(node, (Node *) lfirst(lc)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (lc == NULL)
|
||||||
|
{
|
||||||
|
/* not in list, so add it */
|
||||||
|
pindex++;
|
||||||
|
*context->params_list = lappend(*context->params_list, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(buf, "$%d", pindex);
|
||||||
|
appendStringInfo(buf, "::%s",
|
||||||
|
format_type_with_typemod(node->paramtype,
|
||||||
|
node->paramtypmod));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "(SELECT null::%s)",
|
||||||
|
format_type_with_typemod(node->paramtype,
|
||||||
|
node->paramtypmod));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deparse an array subscript expression.
|
* Deparse an array subscript expression.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseArrayRef(StringInfo buf, ArrayRef *node, PlannerInfo *root)
|
deparseArrayRef(ArrayRef *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
|
StringInfo buf = context->buf;
|
||||||
ListCell *lowlist_item;
|
ListCell *lowlist_item;
|
||||||
ListCell *uplist_item;
|
ListCell *uplist_item;
|
||||||
|
|
||||||
@ -1352,11 +1416,11 @@ deparseArrayRef(StringInfo buf, ArrayRef *node, PlannerInfo *root)
|
|||||||
* case of subscripting a Var, but otherwise do it.
|
* case of subscripting a Var, but otherwise do it.
|
||||||
*/
|
*/
|
||||||
if (IsA(node->refexpr, Var))
|
if (IsA(node->refexpr, Var))
|
||||||
deparseExpr(buf, node->refexpr, root);
|
deparseExpr(node->refexpr, context);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
appendStringInfoChar(buf, '(');
|
appendStringInfoChar(buf, '(');
|
||||||
deparseExpr(buf, node->refexpr, root);
|
deparseExpr(node->refexpr, context);
|
||||||
appendStringInfoChar(buf, ')');
|
appendStringInfoChar(buf, ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1367,11 +1431,11 @@ deparseArrayRef(StringInfo buf, ArrayRef *node, PlannerInfo *root)
|
|||||||
appendStringInfoChar(buf, '[');
|
appendStringInfoChar(buf, '[');
|
||||||
if (lowlist_item)
|
if (lowlist_item)
|
||||||
{
|
{
|
||||||
deparseExpr(buf, lfirst(lowlist_item), root);
|
deparseExpr(lfirst(lowlist_item), context);
|
||||||
appendStringInfoChar(buf, ':');
|
appendStringInfoChar(buf, ':');
|
||||||
lowlist_item = lnext(lowlist_item);
|
lowlist_item = lnext(lowlist_item);
|
||||||
}
|
}
|
||||||
deparseExpr(buf, lfirst(uplist_item), root);
|
deparseExpr(lfirst(uplist_item), context);
|
||||||
appendStringInfoChar(buf, ']');
|
appendStringInfoChar(buf, ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1379,11 +1443,12 @@ deparseArrayRef(StringInfo buf, ArrayRef *node, PlannerInfo *root)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deparse given node which represents a function call into buf.
|
* Deparse a function call.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root)
|
deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
|
StringInfo buf = context->buf;
|
||||||
HeapTuple proctup;
|
HeapTuple proctup;
|
||||||
Form_pg_proc procform;
|
Form_pg_proc procform;
|
||||||
const char *proname;
|
const char *proname;
|
||||||
@ -1397,7 +1462,7 @@ deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root)
|
|||||||
*/
|
*/
|
||||||
if (node->funcformat == COERCE_IMPLICIT_CAST)
|
if (node->funcformat == COERCE_IMPLICIT_CAST)
|
||||||
{
|
{
|
||||||
deparseExpr(buf, (Expr *) linitial(node->args), root);
|
deparseExpr((Expr *) linitial(node->args), context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1413,7 +1478,7 @@ deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root)
|
|||||||
/* Get the typmod if this is a length-coercion function */
|
/* Get the typmod if this is a length-coercion function */
|
||||||
(void) exprIsLengthCoercion((Node *) node, &coercedTypmod);
|
(void) exprIsLengthCoercion((Node *) node, &coercedTypmod);
|
||||||
|
|
||||||
deparseExpr(buf, (Expr *) linitial(node->args), root);
|
deparseExpr((Expr *) linitial(node->args), context);
|
||||||
appendStringInfo(buf, "::%s",
|
appendStringInfo(buf, "::%s",
|
||||||
format_type_with_typemod(rettype, coercedTypmod));
|
format_type_with_typemod(rettype, coercedTypmod));
|
||||||
return;
|
return;
|
||||||
@ -1458,7 +1523,7 @@ deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root)
|
|||||||
appendStringInfoString(buf, ", ");
|
appendStringInfoString(buf, ", ");
|
||||||
if (use_variadic && lnext(arg) == NULL)
|
if (use_variadic && lnext(arg) == NULL)
|
||||||
appendStringInfoString(buf, "VARIADIC ");
|
appendStringInfoString(buf, "VARIADIC ");
|
||||||
deparseExpr(buf, (Expr *) lfirst(arg), root);
|
deparseExpr((Expr *) lfirst(arg), context);
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
appendStringInfoChar(buf, ')');
|
appendStringInfoChar(buf, ')');
|
||||||
@ -1467,12 +1532,13 @@ deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deparse given operator expression into buf. To avoid problems around
|
* Deparse given operator expression. To avoid problems around
|
||||||
* priority of operations, we always parenthesize the arguments.
|
* priority of operations, we always parenthesize the arguments.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseOpExpr(StringInfo buf, OpExpr *node, PlannerInfo *root)
|
deparseOpExpr(OpExpr *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
|
StringInfo buf = context->buf;
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
Form_pg_operator form;
|
Form_pg_operator form;
|
||||||
char oprkind;
|
char oprkind;
|
||||||
@ -1497,7 +1563,7 @@ deparseOpExpr(StringInfo buf, OpExpr *node, PlannerInfo *root)
|
|||||||
if (oprkind == 'r' || oprkind == 'b')
|
if (oprkind == 'r' || oprkind == 'b')
|
||||||
{
|
{
|
||||||
arg = list_head(node->args);
|
arg = list_head(node->args);
|
||||||
deparseExpr(buf, lfirst(arg), root);
|
deparseExpr(lfirst(arg), context);
|
||||||
appendStringInfoChar(buf, ' ');
|
appendStringInfoChar(buf, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1509,7 +1575,7 @@ deparseOpExpr(StringInfo buf, OpExpr *node, PlannerInfo *root)
|
|||||||
{
|
{
|
||||||
arg = list_tail(node->args);
|
arg = list_tail(node->args);
|
||||||
appendStringInfoChar(buf, ' ');
|
appendStringInfoChar(buf, ' ');
|
||||||
deparseExpr(buf, lfirst(arg), root);
|
deparseExpr(lfirst(arg), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
appendStringInfoChar(buf, ')');
|
appendStringInfoChar(buf, ')');
|
||||||
@ -1549,26 +1615,27 @@ deparseOperatorName(StringInfo buf, Form_pg_operator opform)
|
|||||||
* Deparse IS DISTINCT FROM.
|
* Deparse IS DISTINCT FROM.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseDistinctExpr(StringInfo buf, DistinctExpr *node, PlannerInfo *root)
|
deparseDistinctExpr(DistinctExpr *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
|
StringInfo buf = context->buf;
|
||||||
|
|
||||||
Assert(list_length(node->args) == 2);
|
Assert(list_length(node->args) == 2);
|
||||||
|
|
||||||
appendStringInfoChar(buf, '(');
|
appendStringInfoChar(buf, '(');
|
||||||
deparseExpr(buf, linitial(node->args), root);
|
deparseExpr(linitial(node->args), context);
|
||||||
appendStringInfoString(buf, " IS DISTINCT FROM ");
|
appendStringInfoString(buf, " IS DISTINCT FROM ");
|
||||||
deparseExpr(buf, lsecond(node->args), root);
|
deparseExpr(lsecond(node->args), context);
|
||||||
appendStringInfoChar(buf, ')');
|
appendStringInfoChar(buf, ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deparse given ScalarArrayOpExpr expression into buf. To avoid problems
|
* Deparse given ScalarArrayOpExpr expression. To avoid problems
|
||||||
* around priority of operations, we always parenthesize the arguments.
|
* around priority of operations, we always parenthesize the arguments.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseScalarArrayOpExpr(StringInfo buf,
|
deparseScalarArrayOpExpr(ScalarArrayOpExpr *node, deparse_expr_cxt *context)
|
||||||
ScalarArrayOpExpr *node,
|
|
||||||
PlannerInfo *root)
|
|
||||||
{
|
{
|
||||||
|
StringInfo buf = context->buf;
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
Form_pg_operator form;
|
Form_pg_operator form;
|
||||||
Expr *arg1;
|
Expr *arg1;
|
||||||
@ -1588,7 +1655,7 @@ deparseScalarArrayOpExpr(StringInfo buf,
|
|||||||
|
|
||||||
/* Deparse left operand. */
|
/* Deparse left operand. */
|
||||||
arg1 = linitial(node->args);
|
arg1 = linitial(node->args);
|
||||||
deparseExpr(buf, arg1, root);
|
deparseExpr(arg1, context);
|
||||||
appendStringInfoChar(buf, ' ');
|
appendStringInfoChar(buf, ' ');
|
||||||
|
|
||||||
/* Deparse operator name plus decoration. */
|
/* Deparse operator name plus decoration. */
|
||||||
@ -1597,7 +1664,7 @@ deparseScalarArrayOpExpr(StringInfo buf,
|
|||||||
|
|
||||||
/* Deparse right operand. */
|
/* Deparse right operand. */
|
||||||
arg2 = lsecond(node->args);
|
arg2 = lsecond(node->args);
|
||||||
deparseExpr(buf, arg2, root);
|
deparseExpr(arg2, context);
|
||||||
|
|
||||||
appendStringInfoChar(buf, ')');
|
appendStringInfoChar(buf, ')');
|
||||||
|
|
||||||
@ -1611,11 +1678,11 @@ deparseScalarArrayOpExpr(StringInfo buf,
|
|||||||
* Deparse a RelabelType (binary-compatible cast) node.
|
* Deparse a RelabelType (binary-compatible cast) node.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseRelabelType(StringInfo buf, RelabelType *node, PlannerInfo *root)
|
deparseRelabelType(RelabelType *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
deparseExpr(buf, node->arg, root);
|
deparseExpr(node->arg, context);
|
||||||
if (node->relabelformat != COERCE_IMPLICIT_CAST)
|
if (node->relabelformat != COERCE_IMPLICIT_CAST)
|
||||||
appendStringInfo(buf, "::%s",
|
appendStringInfo(context->buf, "::%s",
|
||||||
format_type_with_typemod(node->resulttype,
|
format_type_with_typemod(node->resulttype,
|
||||||
node->resulttypmod));
|
node->resulttypmod));
|
||||||
}
|
}
|
||||||
@ -1627,8 +1694,9 @@ deparseRelabelType(StringInfo buf, RelabelType *node, PlannerInfo *root)
|
|||||||
* into N-argument form, so we'd better be prepared to deal with that.
|
* into N-argument form, so we'd better be prepared to deal with that.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root)
|
deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
|
StringInfo buf = context->buf;
|
||||||
const char *op = NULL; /* keep compiler quiet */
|
const char *op = NULL; /* keep compiler quiet */
|
||||||
bool first;
|
bool first;
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
@ -1643,7 +1711,7 @@ deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root)
|
|||||||
break;
|
break;
|
||||||
case NOT_EXPR:
|
case NOT_EXPR:
|
||||||
appendStringInfoString(buf, "(NOT ");
|
appendStringInfoString(buf, "(NOT ");
|
||||||
deparseExpr(buf, linitial(node->args), root);
|
deparseExpr(linitial(node->args), context);
|
||||||
appendStringInfoChar(buf, ')');
|
appendStringInfoChar(buf, ')');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1654,7 +1722,7 @@ deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root)
|
|||||||
{
|
{
|
||||||
if (!first)
|
if (!first)
|
||||||
appendStringInfo(buf, " %s ", op);
|
appendStringInfo(buf, " %s ", op);
|
||||||
deparseExpr(buf, (Expr *) lfirst(lc), root);
|
deparseExpr((Expr *) lfirst(lc), context);
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
appendStringInfoChar(buf, ')');
|
appendStringInfoChar(buf, ')');
|
||||||
@ -1664,10 +1732,12 @@ deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root)
|
|||||||
* Deparse IS [NOT] NULL expression.
|
* Deparse IS [NOT] NULL expression.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseNullTest(StringInfo buf, NullTest *node, PlannerInfo *root)
|
deparseNullTest(NullTest *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
|
StringInfo buf = context->buf;
|
||||||
|
|
||||||
appendStringInfoChar(buf, '(');
|
appendStringInfoChar(buf, '(');
|
||||||
deparseExpr(buf, node->arg, root);
|
deparseExpr(node->arg, context);
|
||||||
if (node->nulltesttype == IS_NULL)
|
if (node->nulltesttype == IS_NULL)
|
||||||
appendStringInfoString(buf, " IS NULL)");
|
appendStringInfoString(buf, " IS NULL)");
|
||||||
else
|
else
|
||||||
@ -1678,8 +1748,9 @@ deparseNullTest(StringInfo buf, NullTest *node, PlannerInfo *root)
|
|||||||
* Deparse ARRAY[...] construct.
|
* Deparse ARRAY[...] construct.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
deparseArrayExpr(StringInfo buf, ArrayExpr *node, PlannerInfo *root)
|
deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context)
|
||||||
{
|
{
|
||||||
|
StringInfo buf = context->buf;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
|
|
||||||
@ -1688,7 +1759,7 @@ deparseArrayExpr(StringInfo buf, ArrayExpr *node, PlannerInfo *root)
|
|||||||
{
|
{
|
||||||
if (!first)
|
if (!first)
|
||||||
appendStringInfoString(buf, ", ");
|
appendStringInfoString(buf, ", ");
|
||||||
deparseExpr(buf, lfirst(lc), root);
|
deparseExpr(lfirst(lc), context);
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
appendStringInfoChar(buf, ']');
|
appendStringInfoChar(buf, ']');
|
||||||
|
@ -446,6 +446,27 @@ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't
|
|||||||
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
|
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
|
||||||
(4 rows)
|
(4 rows)
|
||||||
|
|
||||||
|
-- parameterized remote path
|
||||||
|
EXPLAIN (VERBOSE, COSTS false)
|
||||||
|
SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
|
||||||
|
QUERY PLAN
|
||||||
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
Nested Loop
|
||||||
|
Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8
|
||||||
|
-> Foreign Scan on public.ft2 a
|
||||||
|
Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8
|
||||||
|
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 47))
|
||||||
|
-> Foreign Scan on public.ft2 b
|
||||||
|
Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8
|
||||||
|
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (($1::integer = "C 1"))
|
||||||
|
(8 rows)
|
||||||
|
|
||||||
|
SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
|
||||||
|
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
|
||||||
|
----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+-----
|
||||||
|
47 | 7 | 00047 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo | 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
|
||||||
|
(1 row)
|
||||||
|
|
||||||
-- ===================================================================
|
-- ===================================================================
|
||||||
-- parameterized queries
|
-- parameterized queries
|
||||||
-- ===================================================================
|
-- ===================================================================
|
||||||
@ -646,7 +667,7 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
|
|||||||
Foreign Scan on public.ft1 t1
|
Foreign Scan on public.ft1 t1
|
||||||
Output: c1, c2, c3, c4, c5, c6, c7, c8
|
Output: c1, c2, c3, c4, c5, c6, c7, c8
|
||||||
Filter: (t1.c8 = $1)
|
Filter: (t1.c8 = $1)
|
||||||
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $2::integer))
|
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer))
|
||||||
(4 rows)
|
(4 rows)
|
||||||
|
|
||||||
EXECUTE st5('foo', 1);
|
EXECUTE st5('foo', 1);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -42,17 +42,20 @@ extern int ExtractConnectionOptions(List *defelems,
|
|||||||
extern void classifyConditions(PlannerInfo *root,
|
extern void classifyConditions(PlannerInfo *root,
|
||||||
RelOptInfo *baserel,
|
RelOptInfo *baserel,
|
||||||
List **remote_conds,
|
List **remote_conds,
|
||||||
List **param_conds,
|
List **local_conds);
|
||||||
List **local_conds,
|
extern bool is_foreign_expr(PlannerInfo *root,
|
||||||
List **param_numbers);
|
RelOptInfo *baserel,
|
||||||
|
Expr *expr);
|
||||||
extern void deparseSelectSql(StringInfo buf,
|
extern void deparseSelectSql(StringInfo buf,
|
||||||
PlannerInfo *root,
|
PlannerInfo *root,
|
||||||
RelOptInfo *baserel,
|
RelOptInfo *baserel,
|
||||||
Bitmapset *attrs_used);
|
Bitmapset *attrs_used);
|
||||||
extern void appendWhereClause(StringInfo buf,
|
extern void appendWhereClause(StringInfo buf,
|
||||||
PlannerInfo *root,
|
PlannerInfo *root,
|
||||||
|
RelOptInfo *baserel,
|
||||||
List *exprs,
|
List *exprs,
|
||||||
bool is_first);
|
bool is_first,
|
||||||
|
List **params);
|
||||||
extern void deparseInsertSql(StringInfo buf, PlannerInfo *root,
|
extern void deparseInsertSql(StringInfo buf, PlannerInfo *root,
|
||||||
Index rtindex, Relation rel,
|
Index rtindex, Relation rel,
|
||||||
List *targetAttrs, List *returningList);
|
List *targetAttrs, List *returningList);
|
||||||
|
@ -189,6 +189,10 @@ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1,
|
|||||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
|
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
|
||||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars
|
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars
|
||||||
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote
|
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote
|
||||||
|
-- parameterized remote path
|
||||||
|
EXPLAIN (VERBOSE, COSTS false)
|
||||||
|
SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
|
||||||
|
SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
|
||||||
|
|
||||||
-- ===================================================================
|
-- ===================================================================
|
||||||
-- parameterized queries
|
-- parameterized queries
|
||||||
|
@ -512,7 +512,7 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
|
|||||||
* be more than one EC that matches the expression; if so it's order-dependent
|
* be more than one EC that matches the expression; if so it's order-dependent
|
||||||
* which one you get. This is annoying but it only happens in corner cases,
|
* which one you get. This is annoying but it only happens in corner cases,
|
||||||
* so for now we live with just reporting the first match. See also
|
* so for now we live with just reporting the first match. See also
|
||||||
* generate_implied_equalities_for_indexcol and match_pathkeys_to_index.)
|
* generate_implied_equalities_for_column and match_pathkeys_to_index.)
|
||||||
*
|
*
|
||||||
* If create_it is TRUE, we'll build a new EquivalenceClass when there is no
|
* If create_it is TRUE, we'll build a new EquivalenceClass when there is no
|
||||||
* match. If create_it is FALSE, we just return NULL when no match.
|
* match. If create_it is FALSE, we just return NULL when no match.
|
||||||
@ -2013,15 +2013,21 @@ mutate_eclass_expressions(PlannerInfo *root,
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* generate_implied_equalities_for_indexcol
|
* generate_implied_equalities_for_column
|
||||||
* Create EC-derived joinclauses usable with a specific index column.
|
* Create EC-derived joinclauses usable with a specific column.
|
||||||
*
|
*
|
||||||
* We assume that any given index column could appear in only one EC.
|
* This is used by indxpath.c to extract potentially indexable joinclauses
|
||||||
|
* from ECs, and can be used by foreign data wrappers for similar purposes.
|
||||||
|
* We assume that only expressions in Vars of a single table are of interest,
|
||||||
|
* but the caller provides a callback function to identify exactly which
|
||||||
|
* such expressions it would like to know about.
|
||||||
|
*
|
||||||
|
* We assume that any given table/index column could appear in only one EC.
|
||||||
* (This should be true in all but the most pathological cases, and if it
|
* (This should be true in all but the most pathological cases, and if it
|
||||||
* isn't, we stop on the first match anyway.) Therefore, what we return
|
* isn't, we stop on the first match anyway.) Therefore, what we return
|
||||||
* is a redundant list of clauses equating the index column to each of
|
* is a redundant list of clauses equating the table/index column to each of
|
||||||
* the other-relation values it is known to be equal to. Any one of
|
* the other-relation values it is known to be equal to. Any one of
|
||||||
* these clauses can be used to create a parameterized indexscan, and there
|
* these clauses can be used to create a parameterized path, and there
|
||||||
* is no value in using more than one. (But it *is* worthwhile to create
|
* is no value in using more than one. (But it *is* worthwhile to create
|
||||||
* a separate parameterized path for each one, since that leads to different
|
* a separate parameterized path for each one, since that leads to different
|
||||||
* join orders.)
|
* join orders.)
|
||||||
@ -2030,13 +2036,13 @@ mutate_eclass_expressions(PlannerInfo *root,
|
|||||||
* to, so as to save the work of creating useless clauses.
|
* to, so as to save the work of creating useless clauses.
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
generate_implied_equalities_for_indexcol(PlannerInfo *root,
|
generate_implied_equalities_for_column(PlannerInfo *root,
|
||||||
IndexOptInfo *index,
|
RelOptInfo *rel,
|
||||||
int indexcol,
|
ec_matches_callback_type callback,
|
||||||
Relids prohibited_rels)
|
void *callback_arg,
|
||||||
|
Relids prohibited_rels)
|
||||||
{
|
{
|
||||||
List *result = NIL;
|
List *result = NIL;
|
||||||
RelOptInfo *rel = index->rel;
|
|
||||||
bool is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
|
bool is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
|
||||||
Index parent_relid;
|
Index parent_relid;
|
||||||
ListCell *lc1;
|
ListCell *lc1;
|
||||||
@ -2069,11 +2075,11 @@ generate_implied_equalities_for_indexcol(PlannerInfo *root,
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Scan members, looking for a match to the indexable column. Note
|
* Scan members, looking for a match to the target column. Note
|
||||||
* that child EC members are considered, but only when they belong to
|
* that child EC members are considered, but only when they belong to
|
||||||
* the target relation. (Unlike regular members, the same expression
|
* the target relation. (Unlike regular members, the same expression
|
||||||
* could be a child member of more than one EC. Therefore, it's
|
* could be a child member of more than one EC. Therefore, it's
|
||||||
* potentially order-dependent which EC a child relation's index
|
* potentially order-dependent which EC a child relation's target
|
||||||
* column gets matched to. This is annoying but it only happens in
|
* column gets matched to. This is annoying but it only happens in
|
||||||
* corner cases, so for now we live with just reporting the first
|
* corner cases, so for now we live with just reporting the first
|
||||||
* match. See also get_eclass_for_sort_expr.)
|
* match. See also get_eclass_for_sort_expr.)
|
||||||
@ -2083,8 +2089,7 @@ generate_implied_equalities_for_indexcol(PlannerInfo *root,
|
|||||||
{
|
{
|
||||||
cur_em = (EquivalenceMember *) lfirst(lc2);
|
cur_em = (EquivalenceMember *) lfirst(lc2);
|
||||||
if (bms_equal(cur_em->em_relids, rel->relids) &&
|
if (bms_equal(cur_em->em_relids, rel->relids) &&
|
||||||
eclass_member_matches_indexcol(cur_ec, cur_em,
|
callback(root, rel, cur_ec, cur_em, callback_arg))
|
||||||
index, indexcol))
|
|
||||||
break;
|
break;
|
||||||
cur_em = NULL;
|
cur_em = NULL;
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,13 @@ typedef struct
|
|||||||
Bitmapset *clauseids; /* quals+preds represented as a bitmapset */
|
Bitmapset *clauseids; /* quals+preds represented as a bitmapset */
|
||||||
} PathClauseUsage;
|
} PathClauseUsage;
|
||||||
|
|
||||||
|
/* Callback argument for ec_member_matches_indexcol */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
IndexOptInfo *index; /* index we're considering */
|
||||||
|
int indexcol; /* index column we want to match to */
|
||||||
|
} ec_member_matches_arg;
|
||||||
|
|
||||||
|
|
||||||
static void consider_index_join_clauses(PlannerInfo *root, RelOptInfo *rel,
|
static void consider_index_join_clauses(PlannerInfo *root, RelOptInfo *rel,
|
||||||
IndexOptInfo *index,
|
IndexOptInfo *index,
|
||||||
@ -162,6 +169,9 @@ static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
|
|||||||
List **clause_columns_p);
|
List **clause_columns_p);
|
||||||
static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
|
static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
|
||||||
int indexcol, Expr *clause, Oid pk_opfamily);
|
int indexcol, Expr *clause, Oid pk_opfamily);
|
||||||
|
static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
|
||||||
|
EquivalenceClass *ec, EquivalenceMember *em,
|
||||||
|
void *arg);
|
||||||
static bool match_boolean_index_clause(Node *clause, int indexcol,
|
static bool match_boolean_index_clause(Node *clause, int indexcol,
|
||||||
IndexOptInfo *index);
|
IndexOptInfo *index);
|
||||||
static bool match_special_index_operator(Expr *clause,
|
static bool match_special_index_operator(Expr *clause,
|
||||||
@ -645,7 +655,7 @@ get_join_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Add applicable eclass join clauses. The clauses generated for each
|
* Add applicable eclass join clauses. The clauses generated for each
|
||||||
* column are redundant (cf generate_implied_equalities_for_indexcol),
|
* column are redundant (cf generate_implied_equalities_for_column),
|
||||||
* so we need at most one. This is the only exception to the general
|
* so we need at most one. This is the only exception to the general
|
||||||
* rule of using all available index clauses.
|
* rule of using all available index clauses.
|
||||||
*/
|
*/
|
||||||
@ -1992,18 +2002,22 @@ match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index,
|
|||||||
|
|
||||||
for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
|
for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
|
||||||
{
|
{
|
||||||
|
ec_member_matches_arg arg;
|
||||||
List *clauses;
|
List *clauses;
|
||||||
|
|
||||||
/* Generate clauses, skipping any that join to lateral_referencers */
|
/* Generate clauses, skipping any that join to lateral_referencers */
|
||||||
clauses = generate_implied_equalities_for_indexcol(root,
|
arg.index = index;
|
||||||
index,
|
arg.indexcol = indexcol;
|
||||||
indexcol,
|
clauses = generate_implied_equalities_for_column(root,
|
||||||
lateral_referencers);
|
index->rel,
|
||||||
|
ec_member_matches_indexcol,
|
||||||
|
(void *) &arg,
|
||||||
|
lateral_referencers);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We have to check whether the results actually do match the index,
|
* We have to check whether the results actually do match the index,
|
||||||
* since for non-btree indexes the EC's equality operators might not
|
* since for non-btree indexes the EC's equality operators might not
|
||||||
* be in the index opclass (cf eclass_member_matches_indexcol).
|
* be in the index opclass (cf ec_member_matches_indexcol).
|
||||||
*/
|
*/
|
||||||
match_clauses_to_index(index, clauses, clauseset);
|
match_clauses_to_index(index, clauses, clauseset);
|
||||||
}
|
}
|
||||||
@ -2682,15 +2696,18 @@ check_partial_indexes(PlannerInfo *root, RelOptInfo *rel)
|
|||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* eclass_member_matches_indexcol
|
* ec_member_matches_indexcol
|
||||||
* Test whether an EquivalenceClass member matches an index column.
|
* Test whether an EquivalenceClass member matches an index column.
|
||||||
*
|
*
|
||||||
* This is exported for use by generate_implied_equalities_for_indexcol.
|
* This is a callback for use by generate_implied_equalities_for_column.
|
||||||
*/
|
*/
|
||||||
bool
|
static bool
|
||||||
eclass_member_matches_indexcol(EquivalenceClass *ec, EquivalenceMember *em,
|
ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
|
||||||
IndexOptInfo *index, int indexcol)
|
EquivalenceClass *ec, EquivalenceMember *em,
|
||||||
|
void *arg)
|
||||||
{
|
{
|
||||||
|
IndexOptInfo *index = ((ec_member_matches_arg *) arg)->index;
|
||||||
|
int indexcol = ((ec_member_matches_arg *) arg)->indexcol;
|
||||||
Oid curFamily = index->opfamily[indexcol];
|
Oid curFamily = index->opfamily[indexcol];
|
||||||
Oid curCollation = index->indexcollations[indexcol];
|
Oid curCollation = index->indexcollations[indexcol];
|
||||||
|
|
||||||
@ -2701,7 +2718,7 @@ eclass_member_matches_indexcol(EquivalenceClass *ec, EquivalenceMember *em,
|
|||||||
* whether clauses generated from the EC could be used with the index, so
|
* whether clauses generated from the EC could be used with the index, so
|
||||||
* don't check the opfamily. This might mean we return "true" for a
|
* don't check the opfamily. This might mean we return "true" for a
|
||||||
* useless EC, so we have to recheck the results of
|
* useless EC, so we have to recheck the results of
|
||||||
* generate_implied_equalities_for_indexcol; see
|
* generate_implied_equalities_for_column; see
|
||||||
* match_eclass_clauses_to_index.
|
* match_eclass_clauses_to_index.
|
||||||
*/
|
*/
|
||||||
if (index->relam == BTREE_AM_OID &&
|
if (index->relam == BTREE_AM_OID &&
|
||||||
|
@ -49,9 +49,6 @@ extern List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
|
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
|
||||||
List *restrictlist,
|
List *restrictlist,
|
||||||
List *exprlist, List *oprlist);
|
List *exprlist, List *oprlist);
|
||||||
extern bool eclass_member_matches_indexcol(EquivalenceClass *ec,
|
|
||||||
EquivalenceMember *em,
|
|
||||||
IndexOptInfo *index, int indexcol);
|
|
||||||
extern bool match_index_to_operand(Node *operand, int indexcol,
|
extern bool match_index_to_operand(Node *operand, int indexcol,
|
||||||
IndexOptInfo *index);
|
IndexOptInfo *index);
|
||||||
extern void expand_indexqual_conditions(IndexOptInfo *index,
|
extern void expand_indexqual_conditions(IndexOptInfo *index,
|
||||||
@ -99,6 +96,12 @@ extern bool have_join_order_restriction(PlannerInfo *root,
|
|||||||
* equivclass.c
|
* equivclass.c
|
||||||
* routines for managing EquivalenceClasses
|
* routines for managing EquivalenceClasses
|
||||||
*/
|
*/
|
||||||
|
typedef bool (*ec_matches_callback_type) (PlannerInfo *root,
|
||||||
|
RelOptInfo *rel,
|
||||||
|
EquivalenceClass *ec,
|
||||||
|
EquivalenceMember *em,
|
||||||
|
void *arg);
|
||||||
|
|
||||||
extern bool process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
|
extern bool process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
|
||||||
bool below_outer_join);
|
bool below_outer_join);
|
||||||
extern Expr *canonicalize_ec_expression(Expr *expr,
|
extern Expr *canonicalize_ec_expression(Expr *expr,
|
||||||
@ -126,10 +129,11 @@ extern void mutate_eclass_expressions(PlannerInfo *root,
|
|||||||
Node *(*mutator) (),
|
Node *(*mutator) (),
|
||||||
void *context,
|
void *context,
|
||||||
bool include_child_exprs);
|
bool include_child_exprs);
|
||||||
extern List *generate_implied_equalities_for_indexcol(PlannerInfo *root,
|
extern List *generate_implied_equalities_for_column(PlannerInfo *root,
|
||||||
IndexOptInfo *index,
|
RelOptInfo *rel,
|
||||||
int indexcol,
|
ec_matches_callback_type callback,
|
||||||
Relids prohibited_rels);
|
void *callback_arg,
|
||||||
|
Relids prohibited_rels);
|
||||||
extern bool have_relevant_eclass_joinclause(PlannerInfo *root,
|
extern bool have_relevant_eclass_joinclause(PlannerInfo *root,
|
||||||
RelOptInfo *rel1, RelOptInfo *rel2);
|
RelOptInfo *rel1, RelOptInfo *rel2);
|
||||||
extern bool has_relevant_eclass_joinclause(PlannerInfo *root,
|
extern bool has_relevant_eclass_joinclause(PlannerInfo *root,
|
||||||
|
Reference in New Issue
Block a user