*
* setrefs.c
* Post-processing of a completed plan tree: fix references to subplan
- * vars, and compute regproc values for operators
+ * vars, compute regproc values for operators, etc
*
- * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.132 2007/02/22 23:44:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.146 2008/10/21 20:42:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "access/transam.h"
+#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
#include "optimizer/tlist.h"
-#include "parser/parse_expr.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
+#include "utils/syscache.h"
typedef struct
{
List *tlist; /* underlying target list */
int num_vars; /* number of plain Var tlist entries */
- bool has_non_vars; /* are there non-plain-Var entries? */
+ bool has_ph_vars; /* are there PlaceHolderVar entries? */
+ bool has_non_vars; /* are there other entries? */
/* array of num_vars entries: */
tlist_vinfo vars[1]; /* VARIABLE LENGTH ARRAY */
} indexed_tlist; /* VARIABLE LENGTH STRUCT */
typedef struct
{
+ PlannerGlobal *glob;
int rtoffset;
} fix_scan_expr_context;
typedef struct
{
+ PlannerGlobal *glob;
indexed_tlist *outer_itlist;
indexed_tlist *inner_itlist;
Index acceptable_rel;
typedef struct
{
+ PlannerGlobal *glob;
indexed_tlist *subplan_itlist;
int rtoffset;
} fix_upper_expr_context;
-#define fix_scan_list(lst, rtoffset) \
- ((List *) fix_scan_expr((Node *) (lst), rtoffset))
+/*
+ * Check if a Const node is a regclass value. We accept plain OID too,
+ * since a regclass Const will get folded to that type if it's an argument
+ * to oideq or similar operators. (This might result in some extraneous
+ * values in a plan's list of relation dependencies, but the worst result
+ * would be occasional useless replans.)
+ */
+#define ISREGCLASSCONST(con) \
+ (((con)->consttype == REGCLASSOID || (con)->consttype == OIDOID) && \
+ !(con)->constisnull)
+
+#define fix_scan_list(glob, lst, rtoffset) \
+ ((List *) fix_scan_expr(glob, (Node *) (lst), rtoffset))
static Plan *set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset);
static Plan *set_subqueryscan_references(PlannerGlobal *glob,
- SubqueryScan *plan,
- int rtoffset);
+ SubqueryScan *plan,
+ int rtoffset);
static bool trivial_subqueryscan(SubqueryScan *plan);
-static Node *fix_scan_expr(Node *node, int rtoffset);
+static Node *fix_scan_expr(PlannerGlobal *glob, Node *node, int rtoffset);
static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
-static void set_join_references(Join *join, int rtoffset);
-static void set_inner_join_references(Plan *inner_plan,
+static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
+static void set_join_references(PlannerGlobal *glob, Join *join, int rtoffset);
+static void set_inner_join_references(PlannerGlobal *glob, Plan *inner_plan,
indexed_tlist *outer_itlist);
-static void set_upper_references(Plan *plan, int rtoffset);
+static void set_upper_references(PlannerGlobal *glob, Plan *plan, int rtoffset);
+static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
static Var *search_indexed_tlist_for_var(Var *var,
indexed_tlist *itlist,
static Var *search_indexed_tlist_for_non_var(Node *node,
indexed_tlist *itlist,
Index newvarno);
-static List *fix_join_expr(List *clauses,
- indexed_tlist *outer_itlist,
- indexed_tlist *inner_itlist,
- Index acceptable_rel, int rtoffset);
+static List *fix_join_expr(PlannerGlobal *glob,
+ List *clauses,
+ indexed_tlist *outer_itlist,
+ indexed_tlist *inner_itlist,
+ Index acceptable_rel, int rtoffset);
static Node *fix_join_expr_mutator(Node *node,
- fix_join_expr_context *context);
-static Node *fix_upper_expr(Node *node,
- indexed_tlist *subplan_itlist,
- int rtoffset);
+ fix_join_expr_context *context);
+static Node *fix_upper_expr(PlannerGlobal *glob,
+ Node *node,
+ indexed_tlist *subplan_itlist,
+ int rtoffset);
static Node *fix_upper_expr_mutator(Node *node,
- fix_upper_expr_context *context);
+ fix_upper_expr_context *context);
static bool fix_opfuncids_walker(Node *node, void *context);
+static bool extract_query_dependencies_walker(Node *node,
+ PlannerGlobal *context);
/*****************************************************************************
* 4. We compute regproc OIDs for operators (ie, we look up the function
* that implements each op).
*
+ * 5. We create lists of specific objects that the plan depends on.
+ * This will be used by plancache.c to drive invalidation of cached plans.
+ * Relation dependencies are represented by OIDs, and everything else by
+ * PlanInvalItems (this distinction is motivated by the shared-inval APIs).
+ * Currently, relations and user-defined functions are the only types of
+ * objects that are explicitly tracked this way.
+ *
* We also perform one final optimization step, which is to delete
* SubqueryScan plan nodes that aren't doing anything useful (ie, have
* no qual and a no-op targetlist). The reason for doing this last is that
* wouldn't match up with the Vars in the outer plan tree. The SubqueryScan
* serves a necessary function as a buffer between outer query and subquery
* variable numbering ... but after we've flattened the rangetable this is
- * no longer a problem, since there's only one rtindex namespace.
+ * no longer a problem, since then there's only one rtindex namespace.
*
* set_plan_references recursively traverses the whole plan tree.
*
* The return value is normally the same Plan node passed in, but can be
* different when the passed-in Plan is a SubqueryScan we decide isn't needed.
*
- * The flattened rangetable entries are appended to glob->finalrtable.
+ * The flattened rangetable entries are appended to glob->finalrtable, and
+ * plan dependencies are appended to glob->relationOids (for relations)
+ * and glob->invalItems (for everything else).
*
* Notice that we modify Plan nodes in-place, but use expression_tree_mutator
- * to process targetlist and qual expressions. We can assume that the Plan
+ * to process targetlist and qual expressions. We can assume that the Plan
* nodes were just built by the planner and are not multiply referenced, but
* it's not so safe to assume that for expression tree nodes.
*/
ListCell *lc;
/*
- * In the flat rangetable, we zero out substructure pointers that are
- * not needed by the executor; this reduces the storage space and
- * copying cost for cached plans.
+ * In the flat rangetable, we zero out substructure pointers that are not
+ * needed by the executor; this reduces the storage space and copying cost
+ * for cached plans. We keep only the alias and eref Alias fields, which
+ * are needed by EXPLAIN.
*/
foreach(lc, rtable)
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
- RangeTblEntry *newrte;
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+ RangeTblEntry *newrte;
/* flat copy to duplicate all the scalar fields */
newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
memcpy(newrte, rte, sizeof(RangeTblEntry));
- /* zap unneeded sub-structure (we keep only the eref Alias) */
+ /* zap unneeded sub-structure */
newrte->subquery = NULL;
+ newrte->joinaliasvars = NIL;
newrte->funcexpr = NULL;
newrte->funccoltypes = NIL;
newrte->funccoltypmods = NIL;
newrte->values_lists = NIL;
- newrte->joinaliasvars = NIL;
- newrte->alias = NULL;
+ newrte->ctecoltypes = NIL;
+ newrte->ctecoltypmods = NIL;
glob->finalrtable = lappend(glob->finalrtable, newrte);
+
+ /*
+ * If it's a plain relation RTE, add the table to relationOids.
+ *
+ * We do this even though the RTE might be unreferenced in the plan
+ * tree; this would correspond to cases such as views that were
+ * expanded, child tables that were eliminated by constraint
+ * exclusion, etc. Schema invalidation on such a rel must still force
+ * rebuilding of the plan.
+ *
+ * Note we don't bother to avoid duplicate list entries. We could,
+ * but it would probably cost more cycles than it would save.
+ */
+ if (newrte->rtekind == RTE_RELATION)
+ glob->relationOids = lappend_oid(glob->relationOids,
+ newrte->relid);
}
/* Now fix the Plan tree */
{
case T_SeqScan:
{
- SeqScan *splan = (SeqScan *) plan;
+ SeqScan *splan = (SeqScan *) plan;
splan->scanrelid += rtoffset;
splan->plan.targetlist =
- fix_scan_list(splan->plan.targetlist, rtoffset);
+ fix_scan_list(glob, splan->plan.targetlist, rtoffset);
splan->plan.qual =
- fix_scan_list(splan->plan.qual, rtoffset);
+ fix_scan_list(glob, splan->plan.qual, rtoffset);
}
break;
case T_IndexScan:
{
- IndexScan *splan = (IndexScan *) plan;
+ IndexScan *splan = (IndexScan *) plan;
splan->scan.scanrelid += rtoffset;
splan->scan.plan.targetlist =
- fix_scan_list(splan->scan.plan.targetlist, rtoffset);
+ fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset);
splan->scan.plan.qual =
- fix_scan_list(splan->scan.plan.qual, rtoffset);
+ fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
splan->indexqual =
- fix_scan_list(splan->indexqual, rtoffset);
+ fix_scan_list(glob, splan->indexqual, rtoffset);
splan->indexqualorig =
- fix_scan_list(splan->indexqualorig, rtoffset);
+ fix_scan_list(glob, splan->indexqualorig, rtoffset);
}
break;
case T_BitmapIndexScan:
Assert(splan->scan.plan.targetlist == NIL);
Assert(splan->scan.plan.qual == NIL);
splan->indexqual =
- fix_scan_list(splan->indexqual, rtoffset);
+ fix_scan_list(glob, splan->indexqual, rtoffset);
splan->indexqualorig =
- fix_scan_list(splan->indexqualorig, rtoffset);
+ fix_scan_list(glob, splan->indexqualorig, rtoffset);
}
break;
case T_BitmapHeapScan:
splan->scan.scanrelid += rtoffset;
splan->scan.plan.targetlist =
- fix_scan_list(splan->scan.plan.targetlist, rtoffset);
+ fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset);
splan->scan.plan.qual =
- fix_scan_list(splan->scan.plan.qual, rtoffset);
+ fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
splan->bitmapqualorig =
- fix_scan_list(splan->bitmapqualorig, rtoffset);
+ fix_scan_list(glob, splan->bitmapqualorig, rtoffset);
}
break;
case T_TidScan:
{
- TidScan *splan = (TidScan *) plan;
+ TidScan *splan = (TidScan *) plan;
splan->scan.scanrelid += rtoffset;
splan->scan.plan.targetlist =
- fix_scan_list(splan->scan.plan.targetlist, rtoffset);
+ fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset);
splan->scan.plan.qual =
- fix_scan_list(splan->scan.plan.qual, rtoffset);
+ fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
splan->tidquals =
- fix_scan_list(splan->tidquals, rtoffset);
+ fix_scan_list(glob, splan->tidquals, rtoffset);
}
break;
case T_SubqueryScan:
splan->scan.scanrelid += rtoffset;
splan->scan.plan.targetlist =
- fix_scan_list(splan->scan.plan.targetlist, rtoffset);
+ fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset);
splan->scan.plan.qual =
- fix_scan_list(splan->scan.plan.qual, rtoffset);
+ fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
splan->funcexpr =
- fix_scan_expr(splan->funcexpr, rtoffset);
+ fix_scan_expr(glob, splan->funcexpr, rtoffset);
}
break;
case T_ValuesScan:
splan->scan.scanrelid += rtoffset;
splan->scan.plan.targetlist =
- fix_scan_list(splan->scan.plan.targetlist, rtoffset);
+ fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset);
splan->scan.plan.qual =
- fix_scan_list(splan->scan.plan.qual, rtoffset);
+ fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
splan->values_lists =
- fix_scan_list(splan->values_lists, rtoffset);
+ fix_scan_list(glob, splan->values_lists, rtoffset);
+ }
+ break;
+ case T_CteScan:
+ {
+ CteScan *splan = (CteScan *) 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_WorkTableScan:
+ {
+ WorkTableScan *splan = (WorkTableScan *) 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:
- set_join_references((Join *) plan, rtoffset);
+ set_join_references(glob, (Join *) plan, rtoffset);
break;
case T_Hash:
/*
* These plan types don't actually bother to evaluate their
* targetlists, because they just return their unmodified input
- * tuples. Even though the targetlist won't be used by the
+ * tuples. Even though the targetlist won't be used by the
* executor, we fix it up for possible use by EXPLAIN (not to
* mention ease of debugging --- wrong varnos are very confusing).
*/
- plan->targetlist =
- fix_scan_list(plan->targetlist, rtoffset);
+ set_dummy_tlist_references(plan, rtoffset);
+
/*
* Since these plan types don't check quals either, we should not
* find any qual expression attached to them.
break;
case T_Limit:
{
- Limit *splan = (Limit *) plan;
+ Limit *splan = (Limit *) plan;
/*
* Like the plan types above, Limit doesn't evaluate its tlist
* or quals. It does have live expressions for limit/offset,
- * however.
+ * however; and those cannot contain subplan variable refs, so
+ * fix_scan_expr works for them.
*/
- splan->plan.targetlist =
- fix_scan_list(splan->plan.targetlist, rtoffset);
+ set_dummy_tlist_references(plan, rtoffset);
Assert(splan->plan.qual == NIL);
+
splan->limitOffset =
- fix_scan_expr(splan->limitOffset, rtoffset);
+ fix_scan_expr(glob, splan->limitOffset, rtoffset);
splan->limitCount =
- fix_scan_expr(splan->limitCount, rtoffset);
+ fix_scan_expr(glob, splan->limitCount, rtoffset);
}
break;
case T_Agg:
case T_Group:
- set_upper_references(plan, rtoffset);
+ set_upper_references(glob, plan, rtoffset);
break;
case T_Result:
{
- Result *splan = (Result *) plan;
+ Result *splan = (Result *) plan;
/*
* Result may or may not have a subplan; if not, it's more
* like a scan node than an upper node.
*/
if (splan->plan.lefttree != NULL)
- set_upper_references(plan, rtoffset);
+ set_upper_references(glob, plan, rtoffset);
else
{
splan->plan.targetlist =
- fix_scan_list(splan->plan.targetlist, rtoffset);
+ fix_scan_list(glob, splan->plan.targetlist, rtoffset);
splan->plan.qual =
- fix_scan_list(splan->plan.qual, rtoffset);
+ fix_scan_list(glob, splan->plan.qual, rtoffset);
}
/* resconstantqual can't contain any subplan variable refs */
splan->resconstantqual =
- fix_scan_expr(splan->resconstantqual, rtoffset);
+ fix_scan_expr(glob, splan->resconstantqual, rtoffset);
}
break;
case T_Append:
{
- Append *splan = (Append *) plan;
+ Append *splan = (Append *) plan;
/*
* Append, like Sort et al, doesn't actually evaluate its
* targetlist or check quals.
*/
- splan->plan.targetlist =
- fix_scan_list(splan->plan.targetlist, rtoffset);
+ set_dummy_tlist_references(plan, rtoffset);
Assert(splan->plan.qual == NIL);
foreach(l, splan->appendplans)
{
}
}
break;
+ case T_RecursiveUnion:
+ /* This doesn't evaluate targetlist or check quals either */
+ set_dummy_tlist_references(plan, rtoffset);
+ Assert(plan->qual == NIL);
+ break;
case T_BitmapAnd:
{
- BitmapAnd *splan = (BitmapAnd *) plan;
+ BitmapAnd *splan = (BitmapAnd *) plan;
/* BitmapAnd works like Append, but has no tlist */
Assert(splan->plan.targetlist == NIL);
break;
case T_BitmapOr:
{
- BitmapOr *splan = (BitmapOr *) plan;
+ BitmapOr *splan = (BitmapOr *) plan;
/* BitmapOr works like Append, but has no tlist */
Assert(splan->plan.targetlist == NIL);
*/
plan->scan.scanrelid += rtoffset;
plan->scan.plan.targetlist =
- fix_scan_list(plan->scan.plan.targetlist, rtoffset);
+ fix_scan_list(glob, plan->scan.plan.targetlist, rtoffset);
plan->scan.plan.qual =
- fix_scan_list(plan->scan.plan.qual, rtoffset);
+ fix_scan_list(glob, plan->scan.plan.qual, rtoffset);
result = (Plan *) plan;
}
return true;
}
+/*
+ * copyVar
+ * Copy a Var node.
+ *
+ * fix_scan_expr and friends do this enough times that it's worth having
+ * a bespoke routine instead of using the generic copyObject() function.
+ */
+static inline Var *
+copyVar(Var *var)
+{
+ Var *newvar = (Var *) palloc(sizeof(Var));
+
+ *newvar = *var;
+ return newvar;
+}
+
+/*
+ * fix_expr_common
+ * Do generic set_plan_references processing on an expression node
+ *
+ * This is code that is common to all variants of expression-fixing.
+ * We must look up operator opcode info for OpExpr and related nodes,
+ * add OIDs from regclass Const nodes into glob->relationOids,
+ * and add catalog TIDs for user-defined functions into glob->invalItems.
+ *
+ * We assume it's okay to update opcode info in-place. So this could possibly
+ * scribble on the planner's input data structures, but it's OK.
+ */
+static void
+fix_expr_common(PlannerGlobal *glob, Node *node)
+{
+ /* We assume callers won't call us on a NULL pointer */
+ if (IsA(node, Aggref))
+ {
+ record_plan_function_dependency(glob,
+ ((Aggref *) node)->aggfnoid);
+ }
+ else if (IsA(node, FuncExpr))
+ {
+ record_plan_function_dependency(glob,
+ ((FuncExpr *) node)->funcid);
+ }
+ else if (IsA(node, OpExpr))
+ {
+ set_opfuncid((OpExpr *) node);
+ record_plan_function_dependency(glob,
+ ((OpExpr *) node)->opfuncid);
+ }
+ else if (IsA(node, DistinctExpr))
+ {
+ set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
+ record_plan_function_dependency(glob,
+ ((DistinctExpr *) node)->opfuncid);
+ }
+ else if (IsA(node, NullIfExpr))
+ {
+ set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
+ record_plan_function_dependency(glob,
+ ((NullIfExpr *) node)->opfuncid);
+ }
+ else if (IsA(node, ScalarArrayOpExpr))
+ {
+ set_sa_opfuncid((ScalarArrayOpExpr *) node);
+ record_plan_function_dependency(glob,
+ ((ScalarArrayOpExpr *) node)->opfuncid);
+ }
+ else if (IsA(node, ArrayCoerceExpr))
+ {
+ if (OidIsValid(((ArrayCoerceExpr *) node)->elemfuncid))
+ record_plan_function_dependency(glob,
+ ((ArrayCoerceExpr *) node)->elemfuncid);
+ }
+ else if (IsA(node, Const))
+ {
+ Const *con = (Const *) node;
+
+ /* Check for regclass reference */
+ if (ISREGCLASSCONST(con))
+ glob->relationOids =
+ lappend_oid(glob->relationOids,
+ DatumGetObjectId(con->constvalue));
+ }
+}
+
/*
* fix_scan_expr
* Do set_plan_references processing on a scan-level expression
*
- * This consists of incrementing all Vars' varnos by rtoffset and
- * looking up operator opcode info for OpExpr and related nodes.
+ * This consists of incrementing all Vars' varnos by rtoffset,
+ * looking up operator opcode info for OpExpr and related nodes,
+ * and adding OIDs from regclass Const nodes into glob->relationOids.
*/
static Node *
-fix_scan_expr(Node *node, int rtoffset)
+fix_scan_expr(PlannerGlobal *glob, Node *node, int rtoffset)
{
fix_scan_expr_context context;
+ context.glob = glob;
context.rtoffset = rtoffset;
- return fix_scan_expr_mutator(node, &context);
+
+ if (rtoffset != 0 || glob->lastPHId != 0)
+ {
+ return fix_scan_expr_mutator(node, &context);
+ }
+ else
+ {
+ /*
+ * If rtoffset == 0, we don't need to change any Vars, and if there
+ * are no placeholders anywhere we won't need to remove them. Then
+ * it's OK to just scribble on the input node tree instead of copying
+ * (since the only change, filling in any unset opfuncid fields,
+ * is harmless). This saves just enough cycles to be noticeable on
+ * trivial queries.
+ */
+ (void) fix_scan_expr_walker(node, &context);
+ return node;
+ }
}
static Node *
return NULL;
if (IsA(node, Var))
{
- Var *var = (Var *) copyObject(node);
+ Var *var = copyVar((Var *) node);
Assert(var->varlevelsup == 0);
+
/*
* We should not see any Vars marked INNER, but in a nestloop inner
- * scan there could be OUTER Vars. Leave them alone.
+ * scan there could be OUTER Vars. Leave them alone.
*/
Assert(var->varno != INNER);
if (var->varno > 0 && var->varno != OUTER)
var->varnoold += context->rtoffset;
return (Node *) var;
}
- /*
- * Since we update opcode info in-place, this part could possibly
- * scribble on the planner's input data structures, but it's OK.
- */
- if (IsA(node, OpExpr))
- set_opfuncid((OpExpr *) node);
- else if (IsA(node, DistinctExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, NullIfExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, ScalarArrayOpExpr))
- set_sa_opfuncid((ScalarArrayOpExpr *) node);
+ if (IsA(node, CurrentOfExpr))
+ {
+ CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
+
+ Assert(cexpr->cvarno != INNER);
+ Assert(cexpr->cvarno != OUTER);
+ cexpr->cvarno += context->rtoffset;
+ return (Node *) cexpr;
+ }
+ if (IsA(node, PlaceHolderVar))
+ {
+ /* At scan level, we should always just evaluate the contained expr */
+ PlaceHolderVar *phv = (PlaceHolderVar *) node;
+
+ return fix_scan_expr_mutator((Node *) phv->phexpr, context);
+ }
+ fix_expr_common(context->glob, node);
return expression_tree_mutator(node, fix_scan_expr_mutator,
(void *) context);
}
+static bool
+fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
+{
+ if (node == NULL)
+ return false;
+ Assert(!IsA(node, PlaceHolderVar));
+ fix_expr_common(context->glob, node);
+ return expression_tree_walker(node, fix_scan_expr_walker,
+ (void *) context);
+}
+
/*
* set_join_references
* Modify the target list and quals of a join node to reference its
* subplans, by setting the varnos to OUTER or INNER and setting attno
* values to the result domain number of either the corresponding outer
* or inner join tuple item. Also perform opcode lookup for these
- * expressions.
+ * expressions. and add regclass OIDs to glob->relationOids.
*
* In the case of a nestloop with inner indexscan, we will also need to
* apply the same transformation to any outer vars appearing in the
* quals of the child indexscan. set_inner_join_references does that.
*/
static void
-set_join_references(Join *join, int rtoffset)
+set_join_references(PlannerGlobal *glob, Join *join, int rtoffset)
{
Plan *outer_plan = join->plan.lefttree;
Plan *inner_plan = join->plan.righttree;
inner_itlist = build_tlist_index(inner_plan->targetlist);
/* All join plans have tlist, qual, and joinqual */
- join->plan.targetlist = fix_join_expr(join->plan.targetlist,
+ join->plan.targetlist = fix_join_expr(glob,
+ join->plan.targetlist,
outer_itlist,
inner_itlist,
(Index) 0,
rtoffset);
- join->plan.qual = fix_join_expr(join->plan.qual,
+ join->plan.qual = fix_join_expr(glob,
+ join->plan.qual,
outer_itlist,
inner_itlist,
(Index) 0,
rtoffset);
- join->joinqual = fix_join_expr(join->joinqual,
+ join->joinqual = fix_join_expr(glob,
+ join->joinqual,
outer_itlist,
inner_itlist,
(Index) 0,
if (IsA(join, NestLoop))
{
/* This processing is split out to handle possible recursion */
- set_inner_join_references(inner_plan, outer_itlist);
+ set_inner_join_references(glob, inner_plan, outer_itlist);
}
else if (IsA(join, MergeJoin))
{
MergeJoin *mj = (MergeJoin *) join;
- mj->mergeclauses = fix_join_expr(mj->mergeclauses,
+ mj->mergeclauses = fix_join_expr(glob,
+ mj->mergeclauses,
outer_itlist,
inner_itlist,
(Index) 0,
{
HashJoin *hj = (HashJoin *) join;
- hj->hashclauses = fix_join_expr(hj->hashclauses,
+ hj->hashclauses = fix_join_expr(glob,
+ hj->hashclauses,
outer_itlist,
inner_itlist,
(Index) 0,
* recursion reaches the inner indexscan, and so we'd have done it twice.
*/
static void
-set_inner_join_references(Plan *inner_plan, indexed_tlist *outer_itlist)
+set_inner_join_references(PlannerGlobal *glob, Plan *inner_plan,
+ indexed_tlist *outer_itlist)
{
if (IsA(inner_plan, IndexScan))
{
Index innerrel = innerscan->scan.scanrelid;
/* only refs to outer vars get changed in the inner qual */
- innerscan->indexqualorig = fix_join_expr(indexqualorig,
+ innerscan->indexqualorig = fix_join_expr(glob,
+ indexqualorig,
outer_itlist,
NULL,
innerrel,
0);
- innerscan->indexqual = fix_join_expr(innerscan->indexqual,
+ innerscan->indexqual = fix_join_expr(glob,
+ innerscan->indexqual,
outer_itlist,
NULL,
innerrel,
* may get rechecked as qpquals).
*/
if (NumRelids((Node *) inner_plan->qual) > 1)
- inner_plan->qual = fix_join_expr(inner_plan->qual,
+ inner_plan->qual = fix_join_expr(glob,
+ inner_plan->qual,
outer_itlist,
NULL,
innerrel,
Index innerrel = innerscan->scan.scanrelid;
/* only refs to outer vars get changed in the inner qual */
- innerscan->indexqualorig = fix_join_expr(indexqualorig,
+ innerscan->indexqualorig = fix_join_expr(glob,
+ indexqualorig,
outer_itlist,
NULL,
innerrel,
0);
- innerscan->indexqual = fix_join_expr(innerscan->indexqual,
+ innerscan->indexqual = fix_join_expr(glob,
+ innerscan->indexqual,
outer_itlist,
NULL,
innerrel,
/* only refs to outer vars get changed in the inner qual */
if (NumRelids((Node *) bitmapqualorig) > 1)
- innerscan->bitmapqualorig = fix_join_expr(bitmapqualorig,
+ innerscan->bitmapqualorig = fix_join_expr(glob,
+ bitmapqualorig,
outer_itlist,
NULL,
innerrel,
* get rechecked as qpquals).
*/
if (NumRelids((Node *) inner_plan->qual) > 1)
- inner_plan->qual = fix_join_expr(inner_plan->qual,
+ inner_plan->qual = fix_join_expr(glob,
+ inner_plan->qual,
outer_itlist,
NULL,
innerrel,
0);
/* Now recurse */
- set_inner_join_references(inner_plan->lefttree, outer_itlist);
+ set_inner_join_references(glob, inner_plan->lefttree, outer_itlist);
}
else if (IsA(inner_plan, BitmapAnd))
{
foreach(l, innerscan->bitmapplans)
{
- set_inner_join_references((Plan *) lfirst(l), outer_itlist);
+ set_inner_join_references(glob, (Plan *) lfirst(l), outer_itlist);
}
}
else if (IsA(inner_plan, BitmapOr))
foreach(l, innerscan->bitmapplans)
{
- set_inner_join_references((Plan *) lfirst(l), outer_itlist);
+ set_inner_join_references(glob, (Plan *) lfirst(l), outer_itlist);
}
}
else if (IsA(inner_plan, TidScan))
TidScan *innerscan = (TidScan *) inner_plan;
Index innerrel = innerscan->scan.scanrelid;
- innerscan->tidquals = fix_join_expr(innerscan->tidquals,
+ innerscan->tidquals = fix_join_expr(glob,
+ innerscan->tidquals,
outer_itlist,
NULL,
innerrel,
foreach(l, appendplan->appendplans)
{
- set_inner_join_references((Plan *) lfirst(l), outer_itlist);
+ set_inner_join_references(glob, (Plan *) lfirst(l), outer_itlist);
}
}
else if (IsA(inner_plan, Result))
Result *result = (Result *) inner_plan;
if (result->plan.lefttree)
- set_inner_join_references(result->plan.lefttree, outer_itlist);
+ set_inner_join_references(glob, result->plan.lefttree, outer_itlist);
}
}
* set_upper_references
* Update the targetlist and quals of an upper-level plan node
* to refer to the tuples returned by its lefttree subplan.
- * Also perform opcode lookup for these expressions.
+ * Also perform opcode lookup for these expressions, and
+ * add regclass OIDs to glob->relationOids.
*
* This is used for single-input plan types like Agg, Group, Result.
*
* the expression.
*/
static void
-set_upper_references(Plan *plan, int rtoffset)
+set_upper_references(PlannerGlobal *glob, Plan *plan, int rtoffset)
{
Plan *subplan = plan->lefttree;
indexed_tlist *subplan_itlist;
List *output_targetlist;
ListCell *l;
- if (subplan != NULL)
- subplan_itlist = build_tlist_index(subplan->targetlist);
- else
- subplan_itlist = build_tlist_index(NIL);
+ subplan_itlist = build_tlist_index(subplan->targetlist);
output_targetlist = NIL;
foreach(l, plan->targetlist)
TargetEntry *tle = (TargetEntry *) lfirst(l);
Node *newexpr;
- newexpr = fix_upper_expr((Node *) tle->expr,
+ newexpr = fix_upper_expr(glob,
+ (Node *) tle->expr,
subplan_itlist,
rtoffset);
tle = flatCopyTargetEntry(tle);
plan->targetlist = output_targetlist;
plan->qual = (List *)
- fix_upper_expr((Node *) plan->qual,
+ fix_upper_expr(glob,
+ (Node *) plan->qual,
subplan_itlist,
rtoffset);
pfree(subplan_itlist);
}
+/*
+ * set_dummy_tlist_references
+ * Replace the targetlist of an upper-level plan node with a simple
+ * list of OUTER references to its child.
+ *
+ * This is used for plan types like Sort and Append that don't evaluate
+ * their targetlists. Although the executor doesn't care at all what's in
+ * the tlist, EXPLAIN needs it to be realistic.
+ *
+ * Note: we could almost use set_upper_references() here, but it fails for
+ * Append for lack of a lefttree subplan. Single-purpose code is faster
+ * anyway.
+ */
+static void
+set_dummy_tlist_references(Plan *plan, int rtoffset)
+{
+ List *output_targetlist;
+ ListCell *l;
+
+ output_targetlist = NIL;
+ foreach(l, plan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Var *oldvar = (Var *) tle->expr;
+ Var *newvar;
+
+ newvar = makeVar(OUTER,
+ tle->resno,
+ exprType((Node *) oldvar),
+ exprTypmod((Node *) oldvar),
+ 0);
+ if (IsA(oldvar, Var))
+ {
+ newvar->varnoold = oldvar->varno + rtoffset;
+ newvar->varoattno = oldvar->varattno;
+ }
+ else
+ {
+ newvar->varnoold = 0; /* wasn't ever a plain Var */
+ newvar->varoattno = 0;
+ }
+
+ tle = flatCopyTargetEntry(tle);
+ tle->expr = (Expr *) newvar;
+ output_targetlist = lappend(output_targetlist, tle);
+ }
+ plan->targetlist = output_targetlist;
+
+ /* We don't touch plan->qual here */
+}
+
+
/*
* build_tlist_index --- build an index data structure for a child tlist
*
list_length(tlist) * sizeof(tlist_vinfo));
itlist->tlist = tlist;
+ itlist->has_ph_vars = false;
itlist->has_non_vars = false;
/* Find the Vars and fill in the index array */
vinfo->resno = tle->resno;
vinfo++;
}
+ else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
+ itlist->has_ph_vars = true;
else
itlist->has_non_vars = true;
}
* build_tlist_index_other_vars --- build a restricted tlist index
*
* This is like build_tlist_index, but we only index tlist entries that
- * are Vars and belong to some rel other than the one specified.
+ * are Vars belonging to some rel other than the one specified. We will set
+ * has_ph_vars (allowing PlaceHolderVars to be matched), but not has_non_vars
+ * (so nothing other than Vars and PlaceHolderVars can be matched).
*/
static indexed_tlist *
build_tlist_index_other_vars(List *tlist, Index ignore_rel)
list_length(tlist) * sizeof(tlist_vinfo));
itlist->tlist = tlist;
+ itlist->has_ph_vars = false;
itlist->has_non_vars = false;
/* Find the desired Vars and fill in the index array */
vinfo++;
}
}
+ else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
+ itlist->has_ph_vars = true;
}
itlist->num_vars = (vinfo - itlist->vars);
if (vinfo->varno == varno && vinfo->varattno == varattno)
{
/* Found a match */
- Var *newvar = (Var *) copyObject(var);
+ Var *newvar = copyVar(var);
newvar->varno = newvarno;
newvar->varattno = vinfo->resno;
* If a match is found, return a Var constructed to reference the tlist item.
* If no match, return NULL.
*
- * NOTE: it is a waste of time to call this if !itlist->has_non_vars
+ * NOTE: it is a waste of time to call this unless itlist->has_ph_vars or
+ * itlist->has_non_vars
*/
static Var *
search_indexed_tlist_for_non_var(Node *node,
* Create a new set of targetlist entries or join qual clauses by
* changing the varno/varattno values of variables in the clauses
* to reference target list values from the outer and inner join
- * relation target lists. Also perform opcode lookup.
+ * relation target lists. Also perform opcode lookup and add
+ * regclass OIDs to glob->relationOids.
*
* This is used in two different scenarios: a normal join clause, where
* all the Vars in the clause *must* be replaced by OUTER or INNER references;
* not modified.
*/
static List *
-fix_join_expr(List *clauses,
+fix_join_expr(PlannerGlobal *glob,
+ List *clauses,
indexed_tlist *outer_itlist,
indexed_tlist *inner_itlist,
Index acceptable_rel,
{
fix_join_expr_context context;
+ context.glob = glob;
context.outer_itlist = outer_itlist;
context.inner_itlist = inner_itlist;
context.acceptable_rel = acceptable_rel;
/* If it's for acceptable_rel, adjust and return it */
if (var->varno == context->acceptable_rel)
{
- var = (Var *) copyObject(var);
+ var = copyVar(var);
var->varno += context->rtoffset;
var->varnoold += context->rtoffset;
return (Node *) var;
/* No referent found for Var */
elog(ERROR, "variable not found in subplan target lists");
}
+ if (IsA(node, PlaceHolderVar))
+ {
+ PlaceHolderVar *phv = (PlaceHolderVar *) node;
+
+ /* See if the PlaceHolderVar has bubbled up from a lower plan node */
+ if (context->outer_itlist->has_ph_vars)
+ {
+ newvar = search_indexed_tlist_for_non_var((Node *) phv,
+ context->outer_itlist,
+ OUTER);
+ if (newvar)
+ return (Node *) newvar;
+ }
+ if (context->inner_itlist && context->inner_itlist->has_ph_vars)
+ {
+ newvar = search_indexed_tlist_for_non_var((Node *) phv,
+ context->inner_itlist,
+ INNER);
+ if (newvar)
+ return (Node *) newvar;
+ }
+
+ /* If not supplied by input plans, evaluate the contained expr */
+ return fix_join_expr_mutator((Node *) phv->phexpr, context);
+ }
/* Try matching more complex expressions too, if tlists have any */
if (context->outer_itlist->has_non_vars)
{
if (newvar)
return (Node *) newvar;
}
- /*
- * Since we update opcode info in-place, this part could possibly
- * scribble on the planner's input data structures, but it's OK.
- */
- if (IsA(node, OpExpr))
- set_opfuncid((OpExpr *) node);
- else if (IsA(node, DistinctExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, NullIfExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, ScalarArrayOpExpr))
- set_sa_opfuncid((ScalarArrayOpExpr *) node);
+ fix_expr_common(context->glob, node);
return expression_tree_mutator(node,
fix_join_expr_mutator,
(void *) context);
/*
* fix_upper_expr
* Modifies an expression tree so that all Var nodes reference outputs
- * of a subplan. Also performs opcode lookup.
+ * of a subplan. Also performs opcode lookup, and adds regclass OIDs to
+ * glob->relationOids.
*
* This is used to fix up target and qual expressions of non-join upper-level
* plan nodes.
* The original tree is not modified.
*/
static Node *
-fix_upper_expr(Node *node,
+fix_upper_expr(PlannerGlobal *glob,
+ Node *node,
indexed_tlist *subplan_itlist,
int rtoffset)
{
fix_upper_expr_context context;
+ context.glob = glob;
context.subplan_itlist = subplan_itlist;
context.rtoffset = rtoffset;
return fix_upper_expr_mutator(node, &context);
elog(ERROR, "variable not found in subplan target list");
return (Node *) newvar;
}
+ if (IsA(node, PlaceHolderVar))
+ {
+ PlaceHolderVar *phv = (PlaceHolderVar *) node;
+
+ /* See if the PlaceHolderVar has bubbled up from a lower plan node */
+ if (context->subplan_itlist->has_ph_vars)
+ {
+ newvar = search_indexed_tlist_for_non_var((Node *) phv,
+ context->subplan_itlist,
+ OUTER);
+ if (newvar)
+ return (Node *) newvar;
+ }
+ /* If not supplied by input plan, evaluate the contained expr */
+ return fix_upper_expr_mutator((Node *) phv->phexpr, context);
+ }
/* Try matching more complex expressions too, if tlist has any */
if (context->subplan_itlist->has_non_vars)
{
if (newvar)
return (Node *) newvar;
}
- /*
- * Since we update opcode info in-place, this part could possibly
- * scribble on the planner's input data structures, but it's OK.
- */
- if (IsA(node, OpExpr))
- set_opfuncid((OpExpr *) node);
- else if (IsA(node, DistinctExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, NullIfExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, ScalarArrayOpExpr))
- set_sa_opfuncid((ScalarArrayOpExpr *) node);
+ fix_expr_common(context->glob, node);
return expression_tree_mutator(node,
fix_upper_expr_mutator,
(void *) context);
* adjusted RETURNING list, result-table Vars will still have their
* original varno, but Vars for other rels will have varno OUTER.
*
- * We also must perform opcode lookup.
+ * We also must perform opcode lookup and add regclass OIDs to
+ * glob->relationOids.
*
* 'rlist': the RETURNING targetlist to be fixed
* 'topplan': the top Plan node for the query (not yet passed through
* they are not coming from a subplan.
*/
List *
-set_returning_clause_references(List *rlist,
+set_returning_clause_references(PlannerGlobal *glob,
+ List *rlist,
Plan *topplan,
Index resultRelation)
{
* We can perform the desired Var fixup by abusing the fix_join_expr
* machinery that normally handles inner indexscan fixup. We search the
* top plan's targetlist for Vars of non-result relations, and use
- * fix_join_expr to convert RETURNING Vars into references to those
- * tlist entries, while leaving result-rel Vars as-is.
+ * fix_join_expr to convert RETURNING Vars into references to those tlist
+ * entries, while leaving result-rel Vars as-is.
+ *
+ * PlaceHolderVars will also be sought in the targetlist, but no
+ * more-complex expressions will be. Note that it is not possible for
+ * a PlaceHolderVar to refer to the result relation, since the result
+ * is never below an outer join. If that case could happen, we'd have
+ * to be prepared to pick apart the PlaceHolderVar and evaluate its
+ * contained expression instead.
*/
itlist = build_tlist_index_other_vars(topplan->targetlist, resultRelation);
- rlist = fix_join_expr(rlist,
+ rlist = fix_join_expr(glob,
+ rlist,
itlist,
NULL,
resultRelation,
if (opexpr->opfuncid == InvalidOid)
opexpr->opfuncid = get_opcode(opexpr->opno);
}
+
+/*****************************************************************************
+ * QUERY DEPENDENCY MANAGEMENT
+ *****************************************************************************/
+
+/*
+ * record_plan_function_dependency
+ * Mark the current plan as depending on a particular function.
+ *
+ * This is exported so that the function-inlining code can record a
+ * dependency on a function that it's removed from the plan tree.
+ */
+void
+record_plan_function_dependency(PlannerGlobal *glob, Oid funcid)
+{
+ /*
+ * For performance reasons, we don't bother to track built-in functions;
+ * we just assume they'll never change (or at least not in ways that'd
+ * invalidate plans using them). For this purpose we can consider a
+ * built-in function to be one with OID less than FirstBootstrapObjectId.
+ * Note that the OID generator guarantees never to generate such an
+ * OID after startup, even at OID wraparound.
+ */
+ if (funcid >= (Oid) FirstBootstrapObjectId)
+ {
+ HeapTuple func_tuple;
+ PlanInvalItem *inval_item;
+
+ func_tuple = SearchSysCache(PROCOID,
+ ObjectIdGetDatum(funcid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(func_tuple))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ inval_item = makeNode(PlanInvalItem);
+
+ /*
+ * It would work to use any syscache on pg_proc, but plancache.c
+ * expects us to use PROCOID.
+ */
+ inval_item->cacheId = PROCOID;
+ inval_item->tupleId = func_tuple->t_self;
+
+ glob->invalItems = lappend(glob->invalItems, inval_item);
+
+ ReleaseSysCache(func_tuple);
+ }
+}
+
+/*
+ * extract_query_dependencies
+ * Given a list of not-yet-planned queries (i.e. Query nodes),
+ * extract their dependencies just as set_plan_references would do.
+ *
+ * This is needed by plancache.c to handle invalidation of cached unplanned
+ * queries.
+ */
+void
+extract_query_dependencies(List *queries,
+ List **relationOids,
+ List **invalItems)
+{
+ PlannerGlobal glob;
+
+ /* Make up a dummy PlannerGlobal so we can use this module's machinery */
+ MemSet(&glob, 0, sizeof(glob));
+ glob.type = T_PlannerGlobal;
+ glob.relationOids = NIL;
+ glob.invalItems = NIL;
+
+ (void) extract_query_dependencies_walker((Node *) queries, &glob);
+
+ *relationOids = glob.relationOids;
+ *invalItems = glob.invalItems;
+}
+
+static bool
+extract_query_dependencies_walker(Node *node, PlannerGlobal *context)
+{
+ if (node == NULL)
+ return false;
+ Assert(!IsA(node, PlaceHolderVar));
+ /* Extract function dependencies and check for regclass Consts */
+ fix_expr_common(context, node);
+ if (IsA(node, Query))
+ {
+ Query *query = (Query *) node;
+ ListCell *lc;
+
+ /* Collect relation OIDs in this Query's rtable */
+ foreach(lc, query->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+ if (rte->rtekind == RTE_RELATION)
+ context->relationOids = lappend_oid(context->relationOids,
+ rte->relid);
+ }
+
+ /* And recurse into the query's subexpressions */
+ return query_tree_walker(query, extract_query_dependencies_walker,
+ (void *) context, 0);
+ }
+ return expression_tree_walker(node, extract_query_dependencies_walker,
+ (void *) context);
+}