blob: cc8fc0e8bb8822643b9e72d493e0c6b111192551 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*-------------------------------------------------------------------------
*
* createplan.c
* Routines to create the desired plan for processing a query.
* Planning is complete, we just need to convert the selected
* Path into a Plan.
*
* Portions Copyright (c) 2005-2008, Greenplum inc
* 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/createplan.c,v 1.217.2.1 2007/07/31 19:53:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <limits.h>
#include "miscadmin.h" /* work_mem */
#include "access/hd_work_mgr.h"
#include "catalog/pg_type.h" /* INT8OID */
#include "nodes/makefuncs.h"
#include "executor/execHHashagg.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/plancat.h"
#include "optimizer/planmain.h"
#include "optimizer/planshare.h"
#include "optimizer/predtest.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "optimizer/subselect.h"
#include "parser/parse_clause.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parsetree.h"
#include "parser/parse_oper.h" /* ordering_oper_opid */
#include "utils/lsyscache.h"
#include "cdb/cdbgroup.h" /* adapt_flow_to_targetlist() */
#include "cdb/cdblink.h" /* getgphostCount() */
#include "cdb/cdbllize.h" /* pull_up_Flow() */
#include "cdb/cdbpath.h" /* cdbpath_rows() */
#include "cdb/cdbpathtoplan.h" /* cdbpathtoplan_create_flow() etc. */
#include "cdb/cdbpullup.h" /* cdbpullup_targetlist() */
#include "cdb/cdbsetop.h" /* mark_passthru_locus() */
#include "cdb/cdbutil.h" /* cdbComponentDatabase, makeRandomSegMap() */
#include "cdb/cdbsreh.h"
typedef struct CreatePlanContext
{
PlannerInfo *root;
} CreatePlanContext;
static Plan *create_subplan(CreatePlanContext *ctx, Path *best_path); /*CDB*/
static Plan *create_scan_plan(CreatePlanContext *ctx, Path *best_path);
List *build_relation_tlist(RelOptInfo *rel);
static bool use_physical_tlist(CreatePlanContext *ctx, RelOptInfo *rel);
static void disuse_physical_tlist(Plan *plan, Path *path);
static Plan *create_gating_plan(CreatePlanContext *ctx, Plan *plan, List *quals);
static Plan *create_join_plan(CreatePlanContext *ctx, JoinPath *best_path);
static Plan *create_append_plan(CreatePlanContext *ctx, AppendPath *best_path);
static Result *create_result_plan(CreatePlanContext *ctx, ResultPath *best_path);
static Material *create_material_plan(CreatePlanContext *ctx, MaterialPath *best_path);
static Plan *create_unique_plan(CreatePlanContext *ctx, UniquePath *best_path);
static Plan *create_motion_plan(CreatePlanContext *ctx, CdbMotionPath *path);
static SeqScan *create_seqscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses);
static ExternalScan *create_externalscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses);
static AppendOnlyScan *create_appendonlyscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses);
static ParquetScan *create_parquetscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses);
static IndexScan *create_indexscan_plan(CreatePlanContext *ctx, IndexPath *best_path,
List *tlist, List *scan_clauses,
List **nonlossy_clauses);
static BitmapHeapScan *create_bitmap_scan_plan(CreatePlanContext *ctx,
BitmapHeapPath *best_path,
List *tlist, List *scan_clauses);
static Plan *create_bitmap_subplan(CreatePlanContext *ctx, Path *bitmapqual,
List **qual, List **indexqual);
static TidScan *create_tidscan_plan(CreatePlanContext *ctx, TidPath *best_path,
List *tlist, List *scan_clauses);
static SubqueryScan *create_subqueryscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses);
static SubqueryScan *create_ctescan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses);
static FunctionScan *create_functionscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses);
static TableFunctionScan *create_tablefunction_plan(CreatePlanContext *ctx,
Path *best_path,
List *tlist,
List *scan_clauses);
static ValuesScan *create_valuesscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses);
static Plan *create_nestloop_plan(CreatePlanContext *ctx, NestPath *best_path,
Plan *outer_plan, Plan *inner_plan);
static MergeJoin *create_mergejoin_plan(CreatePlanContext *ctx, MergePath *best_path,
Plan *outer_plan, Plan *inner_plan);
static HashJoin *create_hashjoin_plan(CreatePlanContext *ctx, HashPath *best_path,
Plan *outer_plan, Plan *inner_plan);
static void fix_indexqual_references(List *indexquals, IndexPath *index_path,
List **fixed_indexquals,
List **nonlossy_indexquals,
List **indexstrategy,
List **indexsubtype);
static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index,
Oid *opclass);
static List *get_switched_clauses(Relids innerrelids, List *clauses);
static List *order_qual_clauses(PlannerInfo *root, List *clauses);
static void copy_path_costsize(PlannerInfo *root, Plan *dest, Path *src);
static void copy_plan_costsize(Plan *dest, Plan *src);
static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
static AppendOnlyScan *make_appendonlyscan(List *qptlist, List *qpqual, Index scanrelid);
static ParquetScan *make_parquetscan(List *qptlist, List *qpqual, Index scanrelid);
static ExternalScan *make_externalscan(List *qptlist,
List *qpqual,
Index scanrelid,
List *filenames,
List *fmtopts,
bool istext,
bool ismasteronly,
int rejectlimit,
bool rejectlimitinrows,
Oid fmterrtableOid,
int encoding);
static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
Oid indexid, List *indexqual, List *indexqualorig,
List *indexstrategy, List *indexsubtype,
ScanDirection indexscandir);
static BitmapIndexScan *make_bitmap_indexscan(Index scanrelid, Oid indexid,
List *indexqual,
List *indexqualorig,
List *indexstrategy,
List *indexsubtype);
static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
List *qpqual,
Plan *lefttree,
List *bitmapqualorig,
Index scanrelid);
static TableFunctionScan* make_tablefunction(List *tlist,
List *scan_quals,
Plan *subplan,
List *subrtable,
Index scanrelid);
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
List *tidquals);
static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
Index scanrelid);
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
Index scanrelid);
static BitmapAnd *make_bitmap_and(List *bitmapplans);
static BitmapOr *make_bitmap_or(List *bitmapplans);
static Sort *make_sort(PlannerInfo *root, Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators);
static List *flatten_grouping_list(List *groupcls);
static char** create_pxf_plan(char **segdb_file_map, RelOptInfo *rel, int total_segs,
CreatePlanContext *ctx, Index scan_relid);
/*
* create_plan
* Creates the access plan for a query by tracing backwards through the
* desired chain of pathnodes, starting at the node 'best_path'. For
* every pathnode found, we create a corresponding plan node containing
* appropriate id, target list, and qualification information.
*
* The tlists and quals in the plan tree are still in planner format,
* ie, Vars still correspond to the parser's numbering. This will be
* fixed later by setrefs.c.
*
* best_path is the best access path
*
* Returns a Plan tree.
*/
Plan *
create_plan(PlannerInfo *root, Path *path)
{
CreatePlanContext ctx;
Plan *plan;
ctx.root = root;
/* Modify path to support unique rowid operation for subquery preds. */
if (root->in_info_list)
cdbpath_dedup_fixup(root, path);
/* Generate the Plan tree. */
plan = create_subplan(&ctx, path);
/* Decorate the top node of the plan with a Flow node. */
plan->flow = cdbpathtoplan_create_flow(root,
path->locus,
path->parent ? path->parent->relids
: NULL,
path->pathkeys,
plan);
return plan;
} /* create_plan */
/*
* create_subplan
*/
Plan *
create_subplan(CreatePlanContext *ctx, Path *best_path)
{
Plan *plan;
switch (best_path->pathtype)
{
case T_SeqScan:
case T_IndexScan:
case T_ExternalScan:
case T_AppendOnlyScan:
case T_ParquetScan:
case T_BitmapHeapScan:
case T_BitmapTableScan:
case T_TidScan:
case T_SubqueryScan:
case T_FunctionScan:
case T_TableFunctionScan:
case T_ValuesScan:
case T_CteScan:
plan = create_scan_plan(ctx, best_path);
break;
case T_HashJoin:
case T_MergeJoin:
case T_NestLoop:
plan = create_join_plan(ctx,
(JoinPath *) best_path);
break;
case T_Append:
plan = create_append_plan(ctx,
(AppendPath *) best_path);
break;
case T_Result:
plan = (Plan *) create_result_plan(ctx,
(ResultPath *) best_path);
break;
case T_Material:
plan = (Plan *) create_material_plan(ctx,
(MaterialPath *) best_path);
break;
case T_Unique:
plan = create_unique_plan(ctx,
(UniquePath *) best_path);
break;
case T_Motion:
plan = create_motion_plan(ctx, (CdbMotionPath *)best_path);
break;
default:
Assert(false);
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
plan = NULL; /* keep compiler quiet */
break;
}
if (CdbPathLocus_IsPartitioned(best_path->locus) ||
CdbPathLocus_IsReplicated(best_path->locus))
plan->dispatch = DISPATCH_PARALLEL;
return plan;
} /* create_subplan */
/*
* create_scan_plan
* Create a scan plan for the parent relation of 'best_path'.
*/
static Plan *
create_scan_plan(CreatePlanContext *ctx, Path *best_path)
{
RelOptInfo *rel = best_path->parent;
List *tlist;
List *scan_clauses;
Plan *plan;
/*
* For table scans, rather than using the relation targetlist (which is
* only those Vars actually needed by the query), we prefer to generate a
* tlist containing all Vars in order. This will allow the executor to
* optimize away projection of the table tuples, if possible. (Note that
* planner.c may replace the tlist we generate here, forcing projection to
* occur.)
*/
if (use_physical_tlist(ctx, rel))
{
tlist = build_physical_tlist(ctx->root, rel);
/* if fail because of dropped cols, use regular method */
if (tlist == NIL)
tlist = build_relation_tlist(rel);
}
else
tlist = build_relation_tlist(rel);
/*
* Extract the relevant restriction clauses from the parent relation. The
* executor must apply all these restrictions during the scan, except for
* pseudoconstants which we'll take care of below.
*/
scan_clauses = rel->baserestrictinfo;
switch (best_path->pathtype)
{
case T_SeqScan:
plan = (Plan *) create_seqscan_plan(ctx,
best_path,
tlist,
scan_clauses);
break;
case T_AppendOnlyScan:
plan = (Plan *) create_appendonlyscan_plan(ctx,
best_path,
tlist,
scan_clauses);
break;
case T_ParquetScan:
plan = (Plan *) create_parquetscan_plan(ctx,
best_path,
tlist,
scan_clauses);
break;
case T_ExternalScan:
plan = (Plan *) create_externalscan_plan(ctx,
best_path,
tlist,
scan_clauses);
break;
case T_IndexScan:
plan = (Plan *) create_indexscan_plan(ctx,
(IndexPath *) best_path,
tlist,
scan_clauses,
NULL);
break;
case T_BitmapHeapScan:
plan = (Plan *) create_bitmap_scan_plan(ctx,
(BitmapHeapPath *) best_path,
tlist,
scan_clauses);
break;
case T_TidScan:
plan = (Plan *) create_tidscan_plan(ctx,
(TidPath *) best_path,
tlist,
scan_clauses);
break;
case T_SubqueryScan:
plan = (Plan *) create_subqueryscan_plan(ctx,
best_path,
tlist,
scan_clauses);
break;
case T_FunctionScan:
plan = (Plan *) create_functionscan_plan(ctx,
best_path,
tlist,
scan_clauses);
break;
case T_TableFunctionScan:
plan = (Plan *) create_tablefunction_plan(ctx,
best_path,
tlist,
scan_clauses);
break;
case T_ValuesScan:
plan = (Plan *) create_valuesscan_plan(ctx,
best_path,
tlist,
scan_clauses);
break;
case T_CteScan:
plan = (Plan *) create_ctescan_plan(ctx,
best_path,
tlist,
scan_clauses);
break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
plan = NULL; /* keep compiler quiet */
break;
}
/* Decorate the top node of the plan with a Flow node. */
plan->flow = cdbpathtoplan_create_flow(ctx->root,
best_path->locus,
best_path->parent ? best_path->parent->relids
: NULL,
best_path->pathkeys,
plan);
/**
* If plan has a flow node, ensure all entries of hashExpr
* are in the targetlist.
*/
if (plan->flow
&& plan->flow->hashExpr)
{
plan->targetlist = add_to_flat_tlist(plan->targetlist, plan->flow->hashExpr, true /* resjunk */);
}
/*
* If there are any pseudoconstant clauses attached to this node, insert a
* gating Result node that evaluates the pseudoconstants as one-time
* quals.
*/
if (ctx->root->hasPseudoConstantQuals)
plan = create_gating_plan(ctx, plan, scan_clauses);
return plan;
}
/*
* Build a target list (ie, a list of TargetEntry) for a relation.
*/
List *
build_relation_tlist(RelOptInfo *rel)
{
List *tlist = NIL;
int resno = 1;
ListCell *v;
foreach(v, rel->reltargetlist)
{
/* Do we really need to copy here? Not sure */
Var *var = (Var *) copyObject(lfirst(v));
tlist = lappend(tlist, makeTargetEntry((Expr *) var,
resno,
NULL,
false));
resno++;
}
return tlist;
}
/*
* use_physical_tlist
* Decide whether to use a tlist matching relation structure,
* rather than only those Vars actually referenced.
*/
static bool
use_physical_tlist(CreatePlanContext *ctx, RelOptInfo *rel)
{
RangeTblEntry *rte;
int i;
/*
* We can do this for real relation scans, subquery scans, function scans,
* and values scans (but not for, eg, joins).
*/
if (rel->rtekind != RTE_RELATION &&
rel->rtekind != RTE_SUBQUERY &&
rel->rtekind != RTE_FUNCTION &&
rel->rtekind != RTE_VALUES &&
rel->rtekind != RTE_TABLEFUNCTION &&
rel->rtekind != RTE_CTE)
return false;
/*
* Can't do it with inheritance cases either (mainly because Append
* doesn't project).
*/
if (rel->reloptkind != RELOPT_BASEREL)
return false;
/*
* Can't do it if any system columns or whole-row Vars are requested,
* either. (This could possibly be fixed but would take some fragile
* assumptions in setrefs.c, I think.)
*/
for (i = rel->min_attr; i <= 0; i++)
{
if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
return false;
}
/* CDB: Don't use physical tlist if rel has pseudo columns. */
rte = rt_fetch(rel->relid, ctx->root->parse->rtable);
if (rte->pseudocols)
return false;
return true;
}
/*
* disuse_physical_tlist
* Switch a plan node back to emitting only Vars actually referenced.
*
* If the plan node immediately above a scan would prefer to get only
* needed Vars and not a physical tlist, it must call this routine to
* undo the decision made by use_physical_tlist(). Currently, Hash, Sort,
* and Material nodes want this, so they don't have to store useless columns.
* We need to ensure that all vars referenced in Flow node, if any, are added
* to the targetlist as resjunk.
*/
static void
disuse_physical_tlist(Plan *plan, Path *path)
{
/* Only need to undo it for path types handled by create_scan_plan() */
switch (path->pathtype)
{
case T_SeqScan:
case T_AppendOnlyScan:
case T_ParquetScan:
case T_ExternalScan:
case T_IndexScan:
case T_BitmapHeapScan:
case T_BitmapTableScan:
case T_TidScan:
case T_SubqueryScan:
case T_FunctionScan:
case T_ValuesScan:
{
plan->targetlist = build_relation_tlist(path->parent);
/**
* If plan has a flow node, ensure all entries of hashExpr
* are in the targetlist.
*/
if (plan->flow
&& plan->flow->hashExpr)
{
plan->targetlist = add_to_flat_tlist(plan->targetlist, plan->flow->hashExpr, true /* resjunk */);
}
}
break;
default:
break;
}
}
/*
* create_gating_plan
* Deal with pseudoconstant qual clauses
*
* If the node's quals list includes any pseudoconstant quals, put them
* into a gating Result node atop the already-built plan. Otherwise,
* return the plan as-is.
*
* Note that we don't change cost or size estimates when doing gating.
* The costs of qual eval were already folded into the plan's startup cost.
* Leaving the size alone amounts to assuming that the gating qual will
* succeed, which is the conservative estimate for planning upper queries.
* We certainly don't want to assume the output size is zero (unless the
* gating qual is actually constant FALSE, and that case is dealt with in
* clausesel.c). Interpolating between the two cases is silly, because
* it doesn't reflect what will really happen at runtime, and besides which
* in most cases we have only a very bad idea of the probability of the gating
* qual being true.
*/
static Plan *
create_gating_plan(CreatePlanContext *ctx, Plan *plan, List *quals)
{
List *pseudoconstants;
/* Pull out any pseudoconstant quals from the RestrictInfo list */
pseudoconstants = extract_actual_clauses(quals, true);
if (!pseudoconstants)
return plan;
pseudoconstants = order_qual_clauses(ctx->root, pseudoconstants);
return (Plan *) make_result((List *) copyObject(plan->targetlist),
(Node *) pseudoconstants,
plan);
}
/*
* create_join_plan
* Create a join plan for 'best_path' and (recursively) plans for its
* inner and outer paths.
*/
static Plan *
create_join_plan(CreatePlanContext *ctx, JoinPath *best_path)
{
Plan *outer_plan;
Plan *inner_plan;
Plan *plan;
Assert(best_path->outerjoinpath);
Assert(best_path->innerjoinpath);
outer_plan = create_subplan(ctx, best_path->outerjoinpath);
inner_plan = create_subplan(ctx, best_path->innerjoinpath);
switch (best_path->path.pathtype)
{
case T_MergeJoin:
plan = (Plan *) create_mergejoin_plan(ctx,
(MergePath *) best_path,
outer_plan,
inner_plan);
break;
case T_HashJoin:
plan = (Plan *) create_hashjoin_plan(ctx,
(HashPath *) best_path,
outer_plan,
inner_plan);
break;
case T_NestLoop:
plan = create_nestloop_plan(ctx,
(NestPath *) best_path,
outer_plan,
inner_plan);
break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->path.pathtype);
plan = NULL; /* keep compiler quiet */
break;
}
plan->flow = cdbpathtoplan_create_flow(ctx->root,
best_path->path.locus,
best_path->path.parent ? best_path->path.parent->relids
: NULL,
best_path->path.pathkeys,
plan);
/**
* If plan has a flow node, ensure all entries of hashExpr
* are in the targetlist.
*/
if (plan->flow
&& plan->flow->hashExpr)
{
plan->targetlist = add_to_flat_tlist(plan->targetlist, plan->flow->hashExpr, true /* resjunk */);
}
/*
* If there are any pseudoconstant clauses attached to this node, insert a
* gating Result node that evaluates the pseudoconstants as one-time
* quals.
*/
if (ctx->root->hasPseudoConstantQuals)
{
/* MPP-2328: don't create a gating plan for the hash-child of
* a NL-join */
if (!IsA(plan, HashJoin) || outer_plan != NULL)
plan = create_gating_plan(ctx, plan, best_path->joinrestrictinfo);
}
#ifdef NOT_USED
/*
* * Expensive function pullups may have pulled local predicates * into
* this path node. Put them in the qpqual of the plan node. * JMH,
* 6/15/92
*/
if (get_loc_restrictinfo(best_path) != NIL)
set_qpqual((Plan) plan,
list_concat(get_qpqual((Plan) plan),
get_actual_clauses(get_loc_restrictinfo(best_path))));
#endif
return plan;
}
/*
* create_append_plan
* Create an Append plan for 'best_path' and (recursively) plans
* for its subpaths.
*
* Returns a Plan node.
*/
static Plan *
create_append_plan(CreatePlanContext *ctx, AppendPath *best_path)
{
Append *plan;
List *tlist = build_relation_tlist(best_path->path.parent);
List *subplans = NIL;
ListCell *subpaths;
/*
* It is possible for the subplans list to contain only one entry, or even
* no entries. Handle these cases specially.
*
* XXX ideally, if there's just one entry, we'd not bother to generate an
* Append node but just return the single child. At the moment this does
* not work because the varno of the child scan plan won't match the
* parent-rel Vars it'll be asked to emit.
*/
if (best_path->subpaths == NIL)
{
/* Generate a Result plan with constant-FALSE gating qual */
return (Plan *) make_result(tlist,
(Node *) list_make1(makeBoolConst(false,
false)),
NULL);
}
/* Normal case with multiple subpaths */
foreach(subpaths, best_path->subpaths)
{
Path *subpath = (Path *) lfirst(subpaths);
subplans = lappend(subplans, create_subplan(ctx, subpath));
}
plan = make_append(subplans, false, tlist);
return (Plan *) plan;
}
/*
* create_result_plan
* Create a Result plan for 'best_path' and (recursively) plans
* for its subpaths.
*
* Returns a Plan node.
*/
static Result *
create_result_plan(CreatePlanContext *ctx, ResultPath *best_path)
{
Result *plan;
List *tlist;
List *quals;
Plan *subplan;
if (best_path->path.parent)
tlist = build_relation_tlist(best_path->path.parent);
else
tlist = NIL; /* will be filled in later */
if (best_path->subpath)
subplan = create_subplan(ctx, best_path->subpath);
else
subplan = NULL;
quals = order_qual_clauses(ctx->root, best_path->quals);
plan = make_result(tlist, (Node *)quals, subplan);
return plan;
}
/*
* create_material_plan
* Create a Material plan for 'best_path' and (recursively) plans
* for its subpaths.
*
* Returns a Plan node.
*/
static Material *
create_material_plan(CreatePlanContext *ctx, MaterialPath *best_path)
{
Material *plan;
Plan *subplan;
subplan = create_subplan(ctx, best_path->subpath);
/* We don't want any excess columns in the materialized tuples */
disuse_physical_tlist(subplan, best_path->subpath);
plan = make_material(subplan);
plan->cdb_strict = best_path->cdb_strict;
copy_path_costsize(ctx->root, &plan->plan, (Path *) best_path);
return plan;
}
/*
* create_unique_plan
* Create a Unique plan for 'best_path' and (recursively) plans
* for its subpaths.
*
* Returns a Plan node.
*/
static Plan *
create_unique_plan(CreatePlanContext *ctx, UniquePath *best_path)
{
Plan *plan;
Plan *subplan;
List *uniq_exprs;
List *newtlist;
int nextresno;
bool newitems;
int numGroupCols;
AttrNumber *groupColIdx;
int groupColPos;
ListCell *l;
subplan = create_subplan(ctx, best_path->subpath);
/* Return naked subplan if we don't need to do any actual unique-ifying */
if (best_path->umethod == UNIQUE_PATH_NOOP)
return subplan;
/* CDB: Result will surely be unique if we produce only its first row. */
if (best_path->umethod == UNIQUE_PATH_LIMIT1)
{
Limit *limitplan;
/* Top off the subplan with a LIMIT 1 node. */
limitplan = makeNode(Limit);
copy_path_costsize(ctx->root, &limitplan->plan, &best_path->path);
limitplan->plan.targetlist = subplan->targetlist;
limitplan->plan.qual = NIL;
limitplan->plan.lefttree = subplan;
limitplan->plan.righttree = NULL;
limitplan->limitOffset = NULL;
limitplan->limitCount = (Node *)makeConst(INT8OID, -1, sizeof(int64),
Int64GetDatum(1),
false, true);
return (Plan *)limitplan;
}
/*----------
* As constructed, the subplan has a "flat" tlist containing just the
* Vars needed here and at upper levels. The values we are supposed
* to unique-ify may be expressions in these variables. We have to
* add any such expressions to the subplan's tlist.
*
* The subplan may have a "physical" tlist if it is a simple scan plan.
* This should be left as-is if we don't need to add any expressions;
* but if we do have to add expressions, then a projection step will be
* needed at runtime anyway, and so we may as well remove unneeded items.
* Therefore newtlist starts from build_relation_tlist() not just a
* copy of the subplan's tlist; and we don't install it into the subplan
* unless stuff has to be added.
*----------
*/
uniq_exprs = best_path->distinct_on_exprs; /*CDB*/
Insist(uniq_exprs);
/* initialize modified subplan tlist as just the "required" vars */
newtlist = build_relation_tlist(best_path->path.parent);
nextresno = list_length(newtlist) + 1;
newitems = false;
foreach(l, uniq_exprs)
{
Node *uniqexpr = lfirst(l);
TargetEntry *tle;
tle = tlist_member(uniqexpr, newtlist);
if (!tle)
{
tle = makeTargetEntry((Expr *) uniqexpr,
nextresno,
NULL,
false);
newtlist = lappend(newtlist, tle);
nextresno++;
newitems = true;
}
}
/*
* If the top plan node can't do projections, we need to add a Result
* node to help it along.
*/
if (newitems)
subplan = plan_pushdown_tlist(subplan, newtlist);
/*
* Build control information showing which subplan output columns are to
* be examined by the grouping step. Unfortunately we can't merge this
* with the previous loop, since we didn't then know which version of the
* subplan tlist we'd end up using.
*/
newtlist = subplan->targetlist;
numGroupCols = list_length(uniq_exprs);
groupColIdx = (AttrNumber *) palloc(numGroupCols * sizeof(AttrNumber));
groupColPos = 0;
foreach(l, uniq_exprs)
{
Node *uniqexpr = lfirst(l);
TargetEntry *tle;
tle = tlist_member(uniqexpr, newtlist);
if (!tle) /* shouldn't happen */
elog(ERROR, "failed to find unique expression in subplan tlist");
groupColIdx[groupColPos++] = tle->resno;
}
if (best_path->umethod == UNIQUE_PATH_HASH)
{
long numGroups;
numGroups = (long) Min(best_path->rows, (double) LONG_MAX);
/*
* Since the Agg node is going to project anyway, we can give it the
* minimum output tlist, without any stuff we might have added to the
* subplan tlist.
*/
plan = (Plan *) make_agg(ctx->root,
build_relation_tlist(best_path->path.parent),
NIL,
AGG_HASHED, false,
numGroupCols,
groupColIdx,
numGroups,
0, /* num_nullcols */
0, /* input_grouping */
0, /* grouping */
0, /* rollup_gs_times */
0,
0,
subplan);
}
else
{
List *sortList = NIL;
for (groupColPos = 0; groupColPos < numGroupCols; groupColPos++)
{
TargetEntry *tle;
tle = get_tle_by_resno(subplan->targetlist,
groupColIdx[groupColPos]);
Assert(tle != NULL);
sortList = addTargetToSortList(NULL, tle,
sortList, subplan->targetlist,
SORTBY_ASC, NIL, false);
}
plan = (Plan *) make_sort_from_sortclauses(ctx->root, sortList, subplan);
plan = (Plan *) make_unique(plan, sortList);
}
/* Adjust output size estimate (other fields should be OK already) */
plan->plan_rows = cdbpath_rows(ctx->root, (Path *)best_path);
return plan;
}
/*
* create_motion_plan
*/
Plan *
create_motion_plan(CreatePlanContext *ctx, CdbMotionPath *path)
{
Motion *motion;
Path *subpath = path->subpath;
Plan *subplan;
/*
* singleQE-->entry: Elide the motion. The subplan will run in the same
* process with its parent: either the qDisp (if it is a top slice) or a
* singleton gang on the entry db (otherwise).
*/
if (CdbPathLocus_IsEntry(path->path.locus) &&
CdbPathLocus_IsSingleQE(subpath->locus))
{
/* Push the MotionPath's locus and pathkeys down onto subpath. */
subpath->locus = path->path.locus;
subpath->pathkeys = path->path.pathkeys;
subplan = create_subplan(ctx, subpath);
return subplan;
}
subplan = create_subplan(ctx, subpath);
/* Only the needed columns should be projected from base rel. */
disuse_physical_tlist(subplan, subpath);
/* Add motion operator. */
motion = cdbpathtoplan_create_motion_plan(ctx->root, path, subplan);
copy_path_costsize(ctx->root, &motion->plan, (Path *)path);
return (Plan *)motion;
} /* create_motion_plan */
/*****************************************************************************
*
* BASE-RELATION SCAN METHODS
*
*****************************************************************************/
/*
* create_seqscan_plan
* Returns a seqscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static SeqScan *
create_seqscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses)
{
SeqScan *scan_plan;
Index scan_relid = best_path->parent->relid;
/* it should be a base rel... */
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_RELATION);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(ctx->root, scan_clauses);
scan_plan = make_seqscan(tlist,
scan_clauses,
scan_relid);
copy_path_costsize(ctx->root, &scan_plan->plan, best_path);
return scan_plan;
}
/*
* create_appendonlyscan_plan
* Returns a appendonlyscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static AppendOnlyScan *
create_appendonlyscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses)
{
AppendOnlyScan *scan_plan;
Index scan_relid = best_path->parent->relid;
/* it should be a base rel... */
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_RELATION);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(ctx->root, scan_clauses);
scan_plan = make_appendonlyscan(tlist,
scan_clauses,
scan_relid);
copy_path_costsize(ctx->root, &scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_parquetscan_plan
* Returns a parquetscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static ParquetScan *
create_parquetscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses)
{
ParquetScan *scan_plan;
Index scan_relid = best_path->parent->relid;
/* it should be a base rel... */
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_RELATION);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(ctx->root, scan_clauses);
scan_plan = make_parquetscan(tlist,
scan_clauses,
scan_relid);
copy_path_costsize(ctx->root, &scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* is_pxf_protocol
* tests if the external table custom protocol is an HADOOP protocol - pxf
*/
bool is_pxf_protocol(Uri *uri)
{
if (uri->protocol != URI_CUSTOM || uri->customprotocol == NULL)
return false;
if (strcmp(uri->customprotocol, "pxf") == 0)
return true;
return false;
}
bool is_hdfs_protocol(Uri *uri)
{
return uri->protocol == URI_HDFS;
}
/*
* create plan for pxf
*/
static char** create_pxf_plan(char **segdb_file_map, RelOptInfo *rel, int total_segs,
CreatePlanContext *ctx, Index scan_relid)
{
int i;
char **segdb_work_map;
int segs_participating = pxf_calc_participating_segments(total_segs);
char *uri_str = (char *) strVal(linitial(rel->locationlist));
if(total_segs > segs_participating)
elog(NOTICE, "External scan using PXF protocol will utilize %d out "
"of %d segment databases", segs_participating, total_segs);
Relation relation = RelationIdGetRelation(planner_rt_fetch(scan_relid, ctx->root)->relid);
if (pxf_enable_filter_pushdown){
segdb_work_map = map_hddata_2gp_segments(uri_str,
total_segs, segs_participating,
relation, (List *) ctx->root->parse->jointree->quals);
}else{
segdb_work_map = map_hddata_2gp_segments(uri_str,
total_segs, segs_participating,
relation, NULL);
}
Assert(segdb_work_map != NULL);
RelationClose(relation);
for (i = 0; i < total_segs; i++)
{
if(segdb_work_map[i] == NULL)
continue;
/*
* We require a data-fragments allocation in
* segdb_work_map for segment i in order to activate segment
* in segdb_file_map[i]
*/
char opt_delim = (strchr(uri_str, '?') == NULL) ? '?' : '&';
StringInfoData seg_uri;
initStringInfoOfSize(&seg_uri, 512);
appendStringInfoString(&seg_uri, uri_str);
appendStringInfoChar(&seg_uri, opt_delim);
appendStringInfoString(&seg_uri, segdb_work_map[i]);
segdb_file_map[i] = seg_uri.data;
}
free_hddata_2gp_segments(segdb_work_map, total_segs);
return segdb_file_map;
}
/*
* create_externalscan_plan
* Returns an externalscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*
* The external plan also includes the data format specification and file
* location specification. Here is where we do the mapping of external file
* to segment database and add it to the plan (or bail out of the mapping
* rules are broken)
*
* Mapping rules
* -------------
* - 'file' protocol: each location (URI of local file) gets mapped to one
* and one only primary segdb.
* - 'http' protocol: each location (URI of http server) gets mapped to one
* and one only primary segdb.
* - 'gpfdist' and 'gpfdists' protocols: all locations (URI of gpfdist(s) client) are mapped
* to all primary segdbs. If there are less URIs than
* segdbs (usually the case) the URIs are duplicated
* so that there will be one for each segdb. However, if
* the GUC variable gp_external_max_segs is set to a num
* less than (total segdbs/total URIs) then we make sure
* that no URI gets mapped to more than this GUC number by
* skipping some segdbs randomly.
* - 'exec' protocol: all segdbs get mapped to execute the command (this is
* soon to be changed though).
* - 'pxf' protocol: has its own mapping logic. See is_pxf_protocol() and
* map_hddata_2gp_segments() below.
*/
static ExternalScan *
create_externalscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses)
{
ExternalScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RelOptInfo *rel = best_path->parent;
Uri *uri = NULL;
List *filenames = NIL;
List *fmtopts = NIL;
List *modifiedloclist = NIL;
ListCell *c = NULL;
char **segdb_file_map = NULL;
char *first_uri_str = NULL;
bool ismasteronly = false;
bool islimitinrows = false;
int rejectlimit = -1;
int encoding = -1;
int total_primaries = 1;
int i;
Oid fmtErrTblOid = InvalidOid;
List *segments = NIL;
ListCell *lc;
bool allocatedResource = (ctx->root->glob->resource != NULL);
/* various processing flags */
bool using_execute = false; /* true if EXECUTE is used */
bool using_location = false; /* true if LOCATION is used */
bool found_candidate = false;
bool found_match = false;
bool done = false;
/* gpfdist(s) or EXECUTE specific variables */
int total_to_skip = 0;
int max_participants_allowed = 0;
int num_segs_participating = 0;
bool *skip_map = NULL;
bool should_skip_randomly = false;
/* it should be an external rel... */
Assert(scan_relid > 0);
Assert(rel->rtekind == RTE_RELATION);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(ctx->root, scan_clauses);
/* get the total valid primary segdb count */
if (allocatedResource)
{
segments = ctx->root->glob->resource->segments;
total_primaries = list_length(segments);
}
/*
* initialize a file-to-segdb mapping. segdb_file_map string array indexes
* segindex and the entries are the external file path is assigned to this
* segment datbase. For example if segdb_file_map[2] has "/tmp/emp.1" then
* this file is assigned to primary segdb 2. if an entry has NULL then that
* segdb isn't assigned any file.
*/
segdb_file_map = (char **) palloc(total_primaries * sizeof(char *));
MemSet(segdb_file_map, 0, total_primaries * sizeof(char *));
Assert(rel->locationlist != NIL);
/* is this an EXECUTE table or a LOCATION (URI) table */
if(rel->execcommand)
{
using_execute = true;
using_location = false;
}
else
{
using_execute = false;
using_location = true;
}
/* various validations */
if(rel->writable && allocatedResource)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("it is not possible to read from a WRITABLE external table."),
errhint("Create the table as READABLE instead"),
errOmitLocation(true)));
if(rel->rejectlimit != -1)
{
/*
* single row error handling is requested, make sure reject
* limit and error table (if requested) are valid.
*
* NOTE: this should never happen unless somebody modified the
* catalog manually. We are just being pedantic here.
*/
VerifyRejectLimit(rel->rejectlimittype, rel->rejectlimit);
}
if(using_execute && !gp_external_enable_exec)
ereport(ERROR,
(errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED), /* any better errcode? */
errmsg("Using external tables with OS level commands "
"(EXECUTE clause) is disabled"),
errhint("To enable set gp_external_enable_exec=on"),
errOmitLocation(true)));
/*
* take a peek at the first URI so we know which protocol we'll deal with
*/
if(!using_execute)
{
first_uri_str = (char *) strVal(lfirst(list_head(rel->locationlist)));
uri = ParseExternalTableUri(first_uri_str);
}
/* in HAWQ 2.0, file protocol is not supported any more. */
if (using_location && (uri->protocol == URI_FILE))
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("the file protocol for external tables is deprecated"),
errhint("use the gpfdist protocol or COPY FROM instead"),
errOmitLocation(true)));
}
/*
* Now we do the actual assignment of work to the segment databases (where
* work is either a URI to open or a command to execute). Due to the
* big differences between the different protocols we handle each one
* separately. Unfortunately this means some code duplication, but keeping
* this separation makes the code much more understandable and (even)
* more maintainable.
*
* Outline of the following code blocks (from simplest to most complex):
* (only one of these will get executed for a statement)
*
* 1) segment mapping for tables with LOCATION http:// or file:// .
*
* These two protocols are very similar in that they enforce a 1-URI:1-segdb
* relationship. The only difference between them is that file:// URI must
* be assigned to a segdb on a host that is local to that URI.
*
* 2) segment mapping for tables with LOCATION gpfdist(s):// or custom protocol
*
* This protocol is more complicated - in here we usually duplicate the user
* supplied gpfdist(s):// URIs until there is one available to every segdb.
* However, in some cases (as determined by gp_external_max_segs GUC) we
* don't want to use *all* segdbs but instead figure out how many and pick
* them randomly (this is mainly for better performance and resource mgmt).
*
* 3) segment mapping for tables with EXECUTE 'cmd' ON.
*
* In here we don't have URI's. We have a single command string and a
* specification of the segdb granularity it should get executed on (the
* ON clause). Depending on the ON clause specification we could go many
* different ways, for example: assign the command to all segdb, or one
* command per host, or assign to 5 random segments, etc...
*/
/* (1) */
if(using_location && uri->protocol == URI_HTTP)
{
/*
* extract file path and name from URI strings and assign them a primary segdb
*/
foreach(c, rel->locationlist)
{
const char *uri_str = (char *) strVal(lfirst(c));
uri = ParseExternalTableUri(uri_str);
found_candidate = false;
found_match = false;
/*
* look through our segment database list and try to find
* a database that can handle this uri.
*/
foreach(lc, segments)
{
Segment *segment = lfirst(lc);
int segind = segment->segindex;
/*
* Assign mapping of external file to this segdb only if:
* 1) This segdb is a valid primary.
* 2) An external file wasn't already assigned to it.
*
* This logic also guarantees that file that appears first in
* the external location list for the same host gets assigned
* the segdb with the lowest index for this host.
*/
/* a valid primary segdb exist on this host */
found_candidate = true;
if(segdb_file_map[segind] == NULL)
{
/* segdb not taken yet. assign this URI to this segdb */
segdb_file_map[segind] = pstrdup(uri_str);
found_match = true;
break;
}
/* too bad. this segdb already has an external source assigned */
}
/*
* We failed to find a segdb for this URI.
*/
if(allocatedResource && (!found_match))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Could not assign a segment database for \"%s\". "
"There are more URIs than total primary segment "
"databases", uri_str),
errOmitLocation(true)));
}
}
} /* PXF */
else if (using_location && uri->protocol == URI_CUSTOM && is_pxf_protocol(uri))
{
segdb_file_map = create_pxf_plan(segdb_file_map, rel, total_primaries, ctx, scan_relid);
}
else if (using_location && is_hdfs_protocol(uri))
{
// nothing to do
}
/* (2) */
else if(using_location && (uri->protocol == URI_GPFDIST ||
uri->protocol == URI_GPFDISTS ||
(uri->protocol == URI_CUSTOM && !is_pxf_protocol(uri)) ))
{
/*
* Re-write the location list for GPFDIST or GPFDISTS before mapping to segments.
*
* If we happen to be dealing with URI's with the 'gpfdist' (or 'gpfdists') protocol
* we do an extra step here.
*
* (*) We modify the locationlist so that every
* primary segdb will get a URI (therefore we duplicate the existing
* URI's until the list is of size = total_primaries).
* Example: 2 URIs, 7 total segdbs.
* Original LocationList: URI1->URI2
* Modified LocationList: URI1->URI2->URI1->URI2->URI1->URI2->URI1
*
* (**) We also make sure that we don't allocate more segdbs than
* (# of URIs x gp_external_max_segs).
* Example: 2 URIs, 7 total segdbs, gp_external_max_segs = 3
* Original LocationList: URI1->URI2
* Modified LocationList: URI1->URI2->URI1->URI2->URI1->URI2 (6 total).
*
* (***) In that case that we need to allocate only a subset of primary
* segdbs and not all we then also create a random map of segments to skip.
* Using the previous example a we create a map of 7 entries and need to
* randomly select 1 segdb to skip (7 - 6 = 1). so it may look like this:
* [F F T F F F F] - in which case we know to skip the 3rd segment only.
*/
/* total num of segs that will participate in the external operation */
num_segs_participating = total_primaries;
/* max num of segs that are allowed to participate in the operation */
if ((uri->protocol == URI_GPFDIST) || (uri->protocol == URI_GPFDISTS))
{
max_participants_allowed = list_length(rel->locationlist) *
gp_external_max_segs;
}
else
{
/* for custom protocol, set max_participants_allowed to num_segs_participating
* so that assignment to segments will use all available segments */
max_participants_allowed = num_segs_participating;
}
elog(DEBUG5,
"num_segs_participating = %d. max_participants_allowed = %d. number of URIs = %d",
num_segs_participating, max_participants_allowed, list_length(rel->locationlist));
/* see (**) above */
if(num_segs_participating > max_participants_allowed)
{
total_to_skip = num_segs_participating - max_participants_allowed;
num_segs_participating = max_participants_allowed;
should_skip_randomly = true;
elog(NOTICE, "External scan %s will utilize %d out "
"of %d segment databases",
(((uri->protocol == URI_GPFDIST) || (uri->protocol == URI_GPFDISTS)) ?
"from gpfdist(s) server" : "using custom protocol"),
num_segs_participating,
total_primaries);
}
if(allocatedResource && (list_length(rel->locationlist) > num_segs_participating))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("There are more external files (URLs) than primary "
"segments that can read them. Found %d URLs and "
"%d primary segments.",list_length(rel->locationlist),
num_segs_participating),
errOmitLocation(true)));
/*
* restart location list and fill in new list until number of
* locations equals the number of segments participating in this
* action (see (*) above for more details).
*/
while(!done)
{
foreach(c, rel->locationlist)
{
char *uri_str = (char *) strVal(lfirst(c));
/* append to a list of Value nodes, size nelems */
modifiedloclist = lappend(modifiedloclist, makeString(pstrdup(uri_str)));
if(list_length(modifiedloclist) >= num_segs_participating)
{
done = true;
break;
}
if(allocatedResource && (list_length(modifiedloclist) > num_segs_participating))
{
elog(ERROR, "External scan location list failed building distribution.");
}
}
}
/* See (***) above for details */
if(should_skip_randomly)
skip_map = makeRandomSegMap(total_primaries, total_to_skip);
/*
* assign each URI from the new location list a primary segdb
*/
foreach(c, modifiedloclist)
{
const char *uri_str = (char *) strVal(lfirst(c));
found_match = false;
/*
* look through our segment database list and try to find
* a database that can handle this uri.
*/
foreach(lc, segments)
{
Segment *segment = lfirst(lc);
int segind = segment->segindex;
/*
* Assign mapping of external file to this segdb only if:
* 1) This segdb is a valid primary.
* 2) An external file wasn't already assigned to it.
*/
/*
* skip this segdb if skip_map for this seg
* index tells us to skip it (set to 'true').
*/
if(should_skip_randomly)
{
Assert(segind < total_primaries);
if(skip_map[segind])
continue; /* skip it */
}
if (segdb_file_map[segind] == NULL) /* segment was not already activated */
{
segdb_file_map[segind] = pstrdup(uri_str);
found_match = true;
break;
}
}
/*
* We failed to find a segdb for this gpfdist(s) URI
* when is_custom_hd is true it means that the HD segment allocation algorithm
* is activated and in this case it is not necessarily true that all segments are allocated
*/
if(allocatedResource && (!found_match))
{
/* should never happen */
elog(LOG, "external tables gpfdist(s) allocation error. "
"total_primaries: %d, num_segs_participating %d "
"max_participants_allowed %d, total_to_skip %d",
total_primaries, num_segs_participating,
max_participants_allowed, total_to_skip);
ereport(ERROR,
(errcode(ERRCODE_GP_INTERNAL_ERROR),
errmsg("Internal error in createplan for external tables"
" when trying to assign segments for gpfdist(s)")));
}
}
}
/* (3) */
else if(using_execute)
{
const char *command = rel->execcommand;
const char *prefix = "execute:";
char *prefixed_command = NULL;
char *on_clause = NULL;
bool match_found = false;
/* build the command string for the executor - 'execute:command' */
StringInfo buf = makeStringInfo();
appendStringInfo(buf, "%s%s", prefix, command);
prefixed_command = pstrdup(buf->data);
pfree(buf->data);
pfree(buf);
buf = NULL;
/* get the ON clause (execute location) information */
on_clause = (char *) strVal(lfirst(list_head(rel->locationlist)));
/*
* Now we handle each one of the ON locations separately:
*
* 1) all segs
* 2) one per host
* 3) all segs on host <foo>
* 4) seg <n> only
* 5) <n> random segs
* 6) master only
*/
if(strcmp(on_clause, "ALL_SEGMENTS") == 0)
{
/* all segments get a copy of the command to execute */
foreach(lc, segments)
{
Segment *segment = lfirst(lc);
int segind = segment->segindex;
segdb_file_map[segind] = pstrdup(prefixed_command);
}
}
else if(strcmp(on_clause, "PER_HOST") == 0)
{
/* 1 seg per host */
List *visited_hosts = NIL;
foreach(lc, segments)
{
Segment *segment = lfirst(lc);
int segind = segment->segindex;
bool host_taken = false;
ListCell *lcc = NULL;
foreach(lcc, visited_hosts)
{
const char *hostname = (char *) strVal(lfirst(lcc));
if (pg_strcasecmp(hostname, segment->hostname) == 0)
{
host_taken = true;
break;
}
}
/*
* if not assigned to a seg on this host before - do it now
* and add this hostname to the list so that we don't use
* segs on this host again.
*/
if (!host_taken)
{
segdb_file_map[segind] = pstrdup(prefixed_command);
visited_hosts = lappend(visited_hosts,
makeString(pstrdup(segment->hostname)));
}
}
}
else if(strncmp(on_clause, "HOST:", strlen("HOST:")) == 0)
{
/* all segs on the specified host get copy of the command */
char *hostname = on_clause + strlen("HOST:");
foreach(lc, segments)
{
Segment *segment = lfirst(lc);
int segind = segment->segindex;
if (pg_strcasecmp(hostname, segment->hostname) == 0)
{
segdb_file_map[segind] = pstrdup(prefixed_command);
match_found = true;
}
}
if (allocatedResource && (!match_found))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Could not assign a segment database for "
"command \"%s\". No valid primary segment was "
"found in the requested host name \"%s\" ",
command, hostname),
errOmitLocation(true)));
}
else if(strncmp(on_clause, "SEGMENT_ID:", strlen("SEGMENT_ID:")) == 0)
{
/* 1 seg with specified id gets a copy of the command */
int target_segid = atoi(on_clause + strlen("SEGMENT_ID:"));
foreach(lc, segments)
{
Segment *segment = lfirst(lc);
int segind = segment->segindex;
if (segind == target_segid)
{
segdb_file_map[segind] = pstrdup(prefixed_command);
match_found = true;
}
}
if(allocatedResource && (!match_found))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Could not assign a segment database for "
"command \"%s\". The requested segment id "
"%d is not a valid primary segment or doesn't "
"exist in the database", command, target_segid),
errOmitLocation(true)));
}
else if(strncmp(on_clause, "TOTAL_SEGS:", strlen("TOTAL_SEGS:")) == 0)
{
/* total n segments selected randomly */
int num_segs_to_use = atoi(on_clause + strlen("TOTAL_SEGS:"));
if(allocatedResource && (num_segs_to_use > total_primaries))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Table defined with EXECUTE ON %d but there "
"are only %d valid primary segments in the "
"database.", num_segs_to_use, total_primaries)));
total_to_skip = total_primaries - num_segs_to_use;
/* skip_map = makeRandomSegMap(total_primaries, total_to_skip); */
{
int tmp_index;
skip_map = (bool *) palloc(total_primaries * sizeof(bool));
MemSet(skip_map, false, total_primaries * sizeof(bool));
for(tmp_index = num_segs_to_use; tmp_index < total_primaries; tmp_index++)
{
skip_map[tmp_index] = true;
}
}
foreach(lc, segments)
{
Segment *segment = lfirst(lc);
int segind = segment->segindex;
Assert(segind < total_primaries);
if(skip_map[segind])
continue; /* skip it */
segdb_file_map[segind] = pstrdup(prefixed_command);
}
}
else if(strcmp(on_clause, "MASTER_ONLY") == 0)
{
/*
* store the command in first array entry and indicate
* that it is meant for the master segment (not seg o).
*/
segdb_file_map[0] = pstrdup(prefixed_command);
ismasteronly = true;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_GP_INTERNAL_ERROR),
errmsg("Internal error in createplan for external tables: "
"got invalid ON clause code %s", on_clause)));
}
}
else
{
/* should never get here */
ereport(ERROR,
(errcode(ERRCODE_GP_INTERNAL_ERROR),
errmsg("Internal error in createplan for external tables")));
}
/*
* convert array map to a list so it can be serialized as part of the plan
*/
for (i = 0; i < total_primaries; i++)
{
if(segdb_file_map[i] != NULL)
filenames = lappend(filenames, makeString(segdb_file_map[i]));
else
{
/* no file for this segdb. add a null entry */
Value *n = makeNode(Value);
n->type = T_Null;
filenames = lappend(filenames, n);
}
}
/* data format description */
Assert(rel->fmtopts);
fmtopts = lappend(fmtopts, makeString(pstrdup(rel->fmtopts)));
/* single row error handling */
if(rel->rejectlimit != -1)
{
islimitinrows = (rel->rejectlimittype == 'r' ? true : false);
rejectlimit = rel->rejectlimit;
fmtErrTblOid = rel->fmterrtbl;
}
/* data encoding */
encoding = rel->ext_encoding;
if (using_location && (is_hdfs_protocol(uri)))
scan_plan = make_externalscan(tlist,
scan_clauses,
scan_relid,
rel->locationlist,
fmtopts,
rel->fmttype,
ismasteronly,
rejectlimit,
islimitinrows,
fmtErrTblOid,
encoding);
else
scan_plan = make_externalscan(tlist,
scan_clauses,
scan_relid,
filenames,
fmtopts,
rel->fmttype,
ismasteronly,
rejectlimit,
islimitinrows,
fmtErrTblOid,
encoding);
copy_path_costsize(ctx->root, &scan_plan->scan.plan, best_path);
pfree(segdb_file_map);
if(skip_map)
pfree(skip_map);
return scan_plan;
}
/*
* Calculate participating_segments for pxf. Calculation is based on gp_external_max_segs
* set by the user, the number of gp hosts - num_hosts and the number of segments - total_segments
* There are three cases:
* a. The guc gp_external_max_segs is greater or equal to the total number of segments:
* participating_segments will be equal to the total number of segments
* b. The guc gp_external_max_segs is between the number of GP hosts and the total number of segments
* In this case we distinguish between two subcases:
* b.1 The guc gp_external_max_segs is a multiplication of the number of hosts:
* participating_segments equals the guc gp_external_max_segs
* b.2 The guc gp_external_max_segs is not a multiply of the number of hosts:
* participating_segments will equal the smallest multiplication of the number of hosts
* that is greater than the guc gp_external_max_segs
* c. The guc gp_external_max_segs is smaller all equal to the number of hosts.
* participating_segments equals the guc gp_external_max_segs
*/
int
pxf_calc_participating_segments(int total_segments)
{
int participating_segments = 0;
int num_hosts = getgphostCount();
if (gp_external_max_segs >= total_segments)
participating_segments = total_segments;
else if ( gp_external_max_segs < total_segments && gp_external_max_segs > num_hosts)
participating_segments = (gp_external_max_segs % num_hosts == 0) ? gp_external_max_segs :
num_hosts * (gp_external_max_segs / num_hosts + 1);
else /* gp_external_max_segs <= num_hosts */
participating_segments = gp_external_max_segs;
return participating_segments;
}
/*
* create_indexscan_plan
* Returns an indexscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*
* The indexquals list of the path contains implicitly-ANDed qual conditions.
* The list can be empty --- then no index restrictions will be applied during
* the scan.
*
* If nonlossy_clauses isn't NULL, *nonlossy_clauses receives a list of the
* nonlossy indexquals.
*/
static IndexScan *
create_indexscan_plan(CreatePlanContext *ctx,
IndexPath *best_path,
List *tlist,
List *scan_clauses,
List **nonlossy_clauses)
{
List *indexquals = best_path->indexquals;
Index baserelid = best_path->path.parent->relid;
Oid indexoid = best_path->indexinfo->indexoid;
List *qpqual;
List *stripped_indexquals;
List *fixed_indexquals;
List *nonlossy_indexquals;
List *indexstrategy;
List *indexsubtype;
ListCell *l;
IndexScan *scan_plan;
/* it should be a base rel... */
Assert(baserelid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
/*
* Build "stripped" indexquals structure (no RestrictInfos) to pass to
* executor as indexqualorig
*/
stripped_indexquals = get_actual_clauses(indexquals);
/*
* The executor needs a copy with the indexkey on the left of each clause
* and with index attr numbers substituted for table ones. This pass also
* gets strategy info and looks for "lossy" operators.
*/
fix_indexqual_references(indexquals, best_path,
&fixed_indexquals,
&nonlossy_indexquals,
&indexstrategy,
&indexsubtype);
/* pass back nonlossy quals if caller wants 'em */
if (nonlossy_clauses)
*nonlossy_clauses = nonlossy_indexquals;
/*
* If this is an innerjoin scan, the indexclauses will contain join
* clauses that are not present in scan_clauses (since the passed-in value
* is just the rel's baserestrictinfo list). We must add these clauses to
* scan_clauses to ensure they get checked. In most cases we will remove
* the join clauses again below, but if a join clause contains a special
* operator, we need to make sure it gets into the scan_clauses.
*
* Note: pointer comparison should be enough to determine RestrictInfo
* matches.
*/
if (best_path->isjoininner)
scan_clauses = list_union_ptr(scan_clauses, best_path->indexclauses);
/*
* The qpqual list must contain all restrictions not automatically handled
* by the index. All the predicates in the indexquals will be checked
* (either by the index itself, or by nodeIndexscan.c), but if there are
* any "special" operators involved then they must be included in qpqual.
* Also, any lossy index operators must be rechecked in the qpqual. The
* upshot is that qpqual must contain scan_clauses minus whatever appears
* in nonlossy_indexquals.
*
* In normal cases simple pointer equality checks will be enough to spot
* duplicate RestrictInfos, so we try that first. In some situations
* (particularly with OR'd index conditions) we may have scan_clauses that
* are not equal to, but are logically implied by, the index quals; so we
* also try a predicate_implied_by() check to see if we can discard quals
* that way. (predicate_implied_by assumes its first input contains only
* immutable functions, so we have to check that.)
*
* We can also discard quals that are implied by a partial index's
* predicate, but only in a plain SELECT; when scanning a target relation
* of UPDATE/DELETE/SELECT FOR UPDATE, we must leave such quals in the
* plan so that they'll be properly rechecked by EvalPlanQual testing.
*
* While at it, we strip off the RestrictInfos to produce a list of plain
* expressions (this loop replaces extract_actual_clauses used in the
* other routines in this file). We have to ignore pseudoconstants.
*/
qpqual = NIL;
foreach(l, scan_clauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
Assert(IsA(rinfo, RestrictInfo));
if (rinfo->pseudoconstant)
continue;
if (list_member_ptr(nonlossy_indexquals, rinfo))
continue;
if (!contain_mutable_functions((Node *) rinfo->clause))
{
List *clausel = list_make1(rinfo->clause);
if (predicate_implied_by(clausel, nonlossy_indexquals))
continue;
if (best_path->indexinfo->indpred)
{
if ((int)baserelid != ctx->root->parse->resultRelation &&
!list_member_int(ctx->root->parse->rowMarks, baserelid))
if (predicate_implied_by(clausel,
best_path->indexinfo->indpred))
continue;
}
}
qpqual = lappend(qpqual, rinfo->clause);
}
/* Sort clauses into best execution order */
qpqual = order_qual_clauses(ctx->root, qpqual);
/* Finally ready to build the plan node */
scan_plan = make_indexscan(tlist,
qpqual,
baserelid,
indexoid,
fixed_indexquals,
stripped_indexquals,
indexstrategy,
indexsubtype,
best_path->indexscandir);
copy_path_costsize(ctx->root, &scan_plan->scan.plan, &best_path->path);
return scan_plan;
}
/*
* create_bitmap_scan_plan
* Returns a bitmap scan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static BitmapHeapScan *
create_bitmap_scan_plan(CreatePlanContext *ctx,
BitmapHeapPath *best_path,
List *tlist,
List *scan_clauses)
{
Index baserelid = best_path->path.parent->relid;
Plan *bitmapqualplan;
List *bitmapqualorig = NULL;
List *indexquals = NULL;
List *qpqual;
ListCell *l;
BitmapHeapScan *scan_plan;
/* it should be a base rel... */
Assert(baserelid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
/* Process the bitmapqual tree into a Plan tree and qual lists */
bitmapqualplan = create_bitmap_subplan(ctx, best_path->bitmapqual,
&bitmapqualorig, &indexquals);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/*
* If this is a innerjoin scan, the indexclauses will contain join clauses
* that are not present in scan_clauses (since the passed-in value is just
* the rel's baserestrictinfo list). We must add these clauses to
* scan_clauses to ensure they get checked. In most cases we will remove
* the join clauses again below, but if a join clause contains a special
* operator, we need to make sure it gets into the scan_clauses.
*/
if (best_path->isjoininner)
{
scan_clauses = list_concat_unique(scan_clauses, bitmapqualorig);
}
/*
* The qpqual list must contain all restrictions not automatically handled
* by the index. All the predicates in the indexquals will be checked
* (either by the index itself, or by nodeBitmapHeapscan.c), but if there
* are any "special" or lossy operators involved then they must be added
* to qpqual. The upshot is that qpqual must contain scan_clauses minus
* whatever appears in indexquals.
*
* In normal cases simple equal() checks will be enough to spot duplicate
* clauses, so we try that first. In some situations (particularly with
* OR'd index conditions) we may have scan_clauses that are not equal to,
* but are logically implied by, the index quals; so we also try a
* predicate_implied_by() check to see if we can discard quals that way.
* (predicate_implied_by assumes its first input contains only immutable
* functions, so we have to check that.)
*
* Unlike create_indexscan_plan(), we need take no special thought here
* for partial index predicates; this is because the predicate conditions
* are already listed in bitmapqualorig and indexquals. Bitmap scans have
* to do it that way because predicate conditions need to be rechecked if
* the scan becomes lossy.
*/
qpqual = NIL;
foreach(l, scan_clauses)
{
Node *clause = (Node *) lfirst(l);
if (list_member(indexquals, clause))
continue;
if (!contain_mutable_functions(clause))
{
List *clausel = list_make1(clause);
if (predicate_implied_by(clausel, indexquals))
continue;
}
qpqual = lappend(qpqual, clause);
}
/* Sort clauses into best execution order */
qpqual = order_qual_clauses(ctx->root, qpqual);
/*
* When dealing with special or lossy operators, we will at this point
* have duplicate clauses in qpqual and bitmapqualorig. We may as well
* drop 'em from bitmapqualorig, since there's no point in making the
* tests twice.
*/
bitmapqualorig = list_difference_ptr(bitmapqualorig, qpqual);
/*
* Copy the finished bitmapqualorig to make sure we have an independent
* copy --- needed in case there are subplans in the index quals
*/
bitmapqualorig = copyObject(bitmapqualorig);
/* Finally ready to build the plan node */
scan_plan = make_bitmap_heapscan(tlist,
qpqual,
bitmapqualplan,
bitmapqualorig,
baserelid);
copy_path_costsize(ctx->root, &scan_plan->scan.plan, &best_path->path);
return scan_plan;
}
/*
* Given a bitmapqual tree, generate the Plan tree that implements it
*
* As byproducts, we also return in *qual and *indexqual the qual lists
* (in implicit-AND form, without RestrictInfos) describing the original index
* conditions and the generated indexqual conditions. The latter is made to
* exclude lossy index operators. Both lists include partial-index predicates,
* because we have to recheck predicates as well as index conditions if the
* bitmap scan becomes lossy.
*
* Note: if you find yourself changing this, you probably need to change
* make_restrictinfo_from_bitmapqual too.
*/
static Plan *
create_bitmap_subplan(CreatePlanContext *ctx, Path *bitmapqual,
List **qual, List **indexqual)
{
Plan *plan;
if (IsA(bitmapqual, BitmapAndPath))
{
BitmapAndPath *apath = (BitmapAndPath *) bitmapqual;
List *subplans = NIL;
List *subquals = NIL;
List *subindexquals = NIL;
ListCell *l;
/*
* There may well be redundant quals among the subplans, since a
* top-level WHERE qual might have gotten used to form several
* different index quals. We don't try exceedingly hard to eliminate
* redundancies, but we do eliminate obvious duplicates by using
* list_concat_unique.
*/
foreach(l, apath->bitmapquals)
{
Plan *subplan;
List *subqual;
List *subindexqual;
subplan = create_bitmap_subplan(ctx, (Path *) lfirst(l),
&subqual, &subindexqual);
subplans = lappend(subplans, subplan);
subquals = list_concat_unique(subquals, subqual);
subindexquals = list_concat_unique(subindexquals, subindexqual);
}
plan = (Plan *) make_bitmap_and(subplans);
plan->startup_cost = apath->path.startup_cost;
plan->total_cost = apath->path.total_cost;
plan->plan_rows =
clamp_row_est(apath->bitmapselectivity * apath->path.parent->tuples);
plan->plan_width = 0; /* meaningless */
*qual = subquals;
*indexqual = subindexquals;
}
else if (IsA(bitmapqual, BitmapOrPath))
{
BitmapOrPath *opath = (BitmapOrPath *) bitmapqual;
List *subplans = NIL;
List *subquals = NIL;
List *subindexquals = NIL;
bool const_true_subqual = false;
bool const_true_subindexqual = false;
ListCell *l;
/*
* Here, we only detect qual-free subplans. A qual-free subplan would
* cause us to generate "... OR true ..." which we may as well reduce
* to just "true". We do not try to eliminate redundant subclauses
* because (a) it's not as likely as in the AND case, and (b) we might
* well be working with hundreds or even thousands of OR conditions,
* perhaps from a long IN list. The performance of list_append_unique
* would be unacceptable.
*/
foreach(l, opath->bitmapquals)
{
Plan *subplan;
List *subqual;
List *subindexqual;
subplan = create_bitmap_subplan(ctx, (Path *) lfirst(l),
&subqual, &subindexqual);
subplans = lappend(subplans, subplan);
if (subqual == NIL)
const_true_subqual = true;
else if (!const_true_subqual)
subquals = lappend(subquals,
make_ands_explicit(subqual));
if (subindexqual == NIL)
const_true_subindexqual = true;
else if (!const_true_subindexqual)
subindexquals = lappend(subindexquals,
make_ands_explicit(subindexqual));
}
/*
* In the presence of ScalarArrayOpExpr quals, we might have built
* BitmapOrPaths with just one subpath; don't add an OR step.
*/
if (list_length(subplans) == 1)
{
plan = (Plan *) linitial(subplans);
}
else
{
plan = (Plan *) make_bitmap_or(subplans);
plan->startup_cost = opath->path.startup_cost;
plan->total_cost = opath->path.total_cost;
plan->plan_rows =
clamp_row_est(opath->bitmapselectivity * opath->path.parent->tuples);
plan->plan_width = 0; /* meaningless */
}
/*
* If there were constant-TRUE subquals, the OR reduces to constant
* TRUE. Also, avoid generating one-element ORs, which could happen
* due to redundancy elimination or ScalarArrayOpExpr quals.
*/
if (const_true_subqual)
*qual = NIL;
else if (list_length(subquals) <= 1)
*qual = subquals;
else
*qual = list_make1(make_orclause(subquals));
if (const_true_subindexqual)
*indexqual = NIL;
else if (list_length(subindexquals) <= 1)
*indexqual = subindexquals;
else
*indexqual = list_make1(make_orclause(subindexquals));
}
else if (IsA(bitmapqual, IndexPath))
{
IndexPath *ipath = (IndexPath *) bitmapqual;
IndexScan *iscan;
List *nonlossy_clauses;
ListCell *l;
/* Use the regular indexscan plan build machinery... */
iscan = create_indexscan_plan(ctx, ipath, NIL, NIL,
&nonlossy_clauses);
/* then convert to a bitmap indexscan */
plan = (Plan *) make_bitmap_indexscan(iscan->scan.scanrelid,
iscan->indexid,
iscan->indexqual,
iscan->indexqualorig,
iscan->indexstrategy,
iscan->indexsubtype);
plan->startup_cost = 0.0;
plan->total_cost = ipath->indextotalcost;
plan->plan_rows =
clamp_row_est(ipath->indexselectivity * ipath->path.parent->tuples);
plan->plan_width = 0; /* meaningless */
*qual = get_actual_clauses(ipath->indexclauses);
*indexqual = get_actual_clauses(nonlossy_clauses);
foreach(l, ipath->indexinfo->indpred)
{
Expr *pred = (Expr *) lfirst(l);
/*
* We know that the index predicate must have been implied by the
* query condition as a whole, but it may or may not be implied by
* the conditions that got pushed into the bitmapqual. Avoid
* generating redundant conditions.
*/
if (!predicate_implied_by(list_make1(pred), ipath->indexclauses))
{
*qual = lappend(*qual, pred);
*indexqual = lappend(*indexqual, pred);
}
}
}
else
{
elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual));
plan = NULL; /* keep compiler quiet */
}
return plan;
}
/*
* create_tidscan_plan
* Returns a tidscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static TidScan *
create_tidscan_plan(CreatePlanContext *ctx, TidPath *best_path,
List *tlist, List *scan_clauses)
{
TidScan *scan_plan;
Index scan_relid = best_path->path.parent->relid;
List *ortidquals;
/* it should be a base rel... */
Assert(scan_relid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/*
* Remove any clauses that are TID quals. This is a bit tricky since the
* tidquals list has implicit OR semantics.
*
* In the case of CURRENT OF, however, we do want the CurrentOfExpr to
* reside in both the tidlist and the qual, as CurrentOfExpr is effectively
* a ctid, gp_segment_id, and tableoid qual. Constant folding will
* finish up this qual rewriting to ensure what we dispatch is a sane interpretation
* of CURRENT OF behavior.
*/
if (!(list_length(scan_clauses) == 1 && IsA(linitial(scan_clauses), CurrentOfExpr)))
{
ortidquals = best_path->tidquals;
if (list_length(ortidquals) > 1)
ortidquals = list_make1(make_orclause(ortidquals));
scan_clauses = list_difference(scan_clauses, ortidquals);
}
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(ctx->root, scan_clauses);
scan_plan = make_tidscan(tlist,
scan_clauses,
scan_relid,
best_path->tidquals);
copy_path_costsize(ctx->root, &scan_plan->scan.plan, &best_path->path);
return scan_plan;
}
/*
* create_subqueryscan_plan
* Returns a subqueryscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static SubqueryScan *
create_subqueryscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses)
{
SubqueryScan *scan_plan;
Index scan_relid = best_path->parent->relid;
/* it should be a subquery base rel... */
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_SUBQUERY);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(ctx->root, scan_clauses);
scan_plan = make_subqueryscan(ctx->root, tlist,
scan_clauses,
scan_relid,
best_path->parent->subplan,
best_path->parent->subrtable);
copy_path_costsize(ctx->root, &scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_ctescan_plan
* Returns a ctescan plan for the base relatioon scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static SubqueryScan *
create_ctescan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses)
{
Assert(best_path->parent->rtekind == RTE_CTE);
Index scan_relid = best_path->parent->relid;
Assert(scan_relid > 0);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(ctx->root, scan_clauses);
SubqueryScan *scan_plan = make_subqueryscan(ctx->root, tlist,
scan_clauses,
scan_relid,
best_path->parent->subplan,
best_path->parent->subrtable);
copy_path_costsize(ctx->root, &scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_functionscan_plan
* Returns a functionscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static FunctionScan *
create_functionscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses)
{
FunctionScan *scan_plan;
Index scan_relid = best_path->parent->relid;
/* it should be a function base rel... */
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_FUNCTION);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(ctx->root, scan_clauses);
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid);
copy_path_costsize(ctx->root, &scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* create_tablefunction_plan
* Returns a TableFunction plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static TableFunctionScan *
create_tablefunction_plan(CreatePlanContext *ctx,
Path *best_path,
List *tlist,
List *scan_clauses)
{
TableFunctionScan *tablefunc;
Plan *subplan = best_path->parent->subplan;
List *subrtable = best_path->parent->subrtable;
Index scan_relid = best_path->parent->relid;
/* it should be a function base rel... */
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_TABLEFUNCTION);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Create the TableFunctionScan plan */
tablefunc = make_tablefunction(tlist, scan_clauses, subplan, subrtable,
scan_relid);
/* Cost is determined largely by the cost of the underlying subplan */
copy_plan_costsize(&tablefunc->scan.plan, subplan);
copy_path_costsize(ctx->root, &tablefunc->scan.plan, best_path);
return tablefunc;
}
/*
* create_valuesscan_plan
* Returns a valuesscan plan for the base relation scanned by 'best_path'
* with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/
static ValuesScan *
create_valuesscan_plan(CreatePlanContext *ctx, Path *best_path,
List *tlist, List *scan_clauses)
{
ValuesScan *scan_plan;
Index scan_relid = best_path->parent->relid;
/* it should be a values base rel... */
Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_VALUES);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(ctx->root, scan_clauses);
scan_plan = make_valuesscan(tlist, scan_clauses, scan_relid);
copy_path_costsize(ctx->root, &scan_plan->scan.plan, best_path);
return scan_plan;
}
/*
* remove_isnotfalse_expr
*/
static Expr *
remove_isnotfalse_expr(Expr *expr)
{
if (IsA(expr, BooleanTest))
{
BooleanTest *bt = (BooleanTest *) expr;
if (bt->booltesttype == IS_NOT_FALSE)
{
return bt->arg;
}
}
return expr;
}
/*
* remove_isnotfalse
* Given a list of joinclauses, extract the bare clauses, removing any IS_NOT_FALSE
* additions. The original data structure is not touched; a modified list is returned
*/
static List *
remove_isnotfalse(List *clauses)
{
List *t_list = NIL;
ListCell *l;
foreach (l, clauses)
{
Node *node = (Node *) lfirst(l);
if (IsA(node, Expr) || IsA(node, BooleanTest))
{
Expr *expr = (Expr *) node;
expr = remove_isnotfalse_expr(expr);
t_list = lappend(t_list, expr);
}
else if (IsA(node, RestrictInfo))
{
RestrictInfo *restrictinfo = (RestrictInfo *) node;
Expr *rclause = restrictinfo->clause;
rclause = remove_isnotfalse_expr(rclause);
t_list = lappend(t_list, rclause);
}
else
{
t_list = lappend(t_list, node);
}
}
return t_list;
}
/*****************************************************************************
*
* JOIN METHODS
*
*****************************************************************************/
static Plan *
create_nestloop_plan(CreatePlanContext *ctx,
NestPath *best_path,
Plan *outer_plan,
Plan *inner_plan)
{
List *tlist = build_relation_tlist(best_path->jpath.path.parent);
List *joinrestrictclauses = best_path->jpath.joinrestrictinfo;
List *joinclauses;
List *otherclauses;
NestLoop *join_plan = NULL;
bool prefetch = false;
/* MPP-1459: subqueries are resolved after our deadlock checks in
* pathnode.c; so we have to check here to make sure that we catch
* all motion deadlocks.
*
* MPP-1487: if there is already a materialize node here, we don't
* want to insert another one. :-)
*
* NOTE: materialize_finished_plan() does *almost* what we want --
* except we aren't finished. */
if (!IsA(best_path->jpath.innerjoinpath, MaterialPath) &&
(best_path->jpath.innerjoinpath->motionHazard ||
!best_path->jpath.innerjoinpath->rescannable))
{
Material *mat;
Path matpath; /* dummy for cost fixup */
mat = make_material(inner_plan);
/* Set cost data */
cost_material(&matpath,
ctx->root,
inner_plan->total_cost,
inner_plan->plan_rows,
inner_plan->plan_width);
mat->plan.startup_cost = matpath.startup_cost;
mat->plan.total_cost = matpath.total_cost;
mat->plan.plan_rows = inner_plan->plan_rows;
mat->plan.plan_width = inner_plan->plan_width;
if (best_path->jpath.outerjoinpath->motionHazard)
{
mat->cdb_strict = true;
prefetch = true;
}
inner_plan = (Plan *)mat;
}
/* MPP-1657: if there is already a materialize here, we may need
* to update its strictness. */
else if (IsA(best_path->jpath.innerjoinpath, MaterialPath) &&
best_path->jpath.innerjoinpath->motionHazard &&
best_path->jpath.outerjoinpath->motionHazard)
{
Material *mat = (Material *)inner_plan;
prefetch = true;
mat->cdb_strict = true;
}
if (IsA(best_path->jpath.innerjoinpath, IndexPath))
{
/*
* An index is being used to reduce the number of tuples scanned in
* the inner relation. If there are join clauses being used with the
* index, we may remove those join clauses from the list of clauses
* that have to be checked as qpquals at the join node.
*
* We can also remove any join clauses that are redundant with those
* being used in the index scan; prior redundancy checks will not have
* caught this case because the join clauses would never have been put
* in the same joininfo list.
*
* We can skip this if the index path is an ordinary indexpath and not
* a special innerjoin path.
*/
IndexPath *innerpath = (IndexPath *) best_path->jpath.innerjoinpath;
if (innerpath->isjoininner)
{
joinrestrictclauses =
select_nonredundant_join_clauses(ctx->root,
joinrestrictclauses,
innerpath->indexclauses,
best_path->jpath.outerjoinpath->parent->relids,
best_path->jpath.innerjoinpath->parent->relids,
IS_OUTER_JOIN(best_path->jpath.jointype));
}
}
else if (IsA(best_path->jpath.innerjoinpath, BitmapHeapPath) ||
IsA(best_path->jpath.innerjoinpath, BitmapAppendOnlyPath))
{
/*
* Same deal for bitmapped index scans.
*
* Note: both here and above, we ignore any implicit index
* restrictions associated with the use of partial indexes. This is
* OK because we're only trying to prove we can dispense with some
* join quals; failing to prove that doesn't result in an incorrect
* plan. It is the right way to proceed because adding more quals to
* the stuff we got from the original query would just make it harder
* to detect duplication. (Also, to change this we'd have to be wary
* of UPDATE/DELETE/SELECT FOR UPDATE target relations; see notes
* above about EvalPlanQual.)
*/
bool isjoininner = false;
Path *bitmapqual = NULL;
if (IsA(best_path->jpath.innerjoinpath, BitmapHeapPath))
{
BitmapHeapPath *innerpath = (BitmapHeapPath *) best_path->jpath.innerjoinpath;
isjoininner = innerpath->isjoininner;
bitmapqual = innerpath->bitmapqual;
}
else
{
Assert(IsA(best_path->jpath.innerjoinpath, BitmapAppendOnlyPath));
BitmapAppendOnlyPath *innerpath = (BitmapAppendOnlyPath *) best_path->jpath.innerjoinpath;
isjoininner = innerpath->isjoininner;
bitmapqual = innerpath->bitmapqual;
}
if (isjoininner)
{
List *bitmapclauses;
bitmapclauses =
make_restrictinfo_from_bitmapqual(bitmapqual,
true,
false);
joinrestrictclauses =
select_nonredundant_join_clauses(ctx->root,
joinrestrictclauses,
bitmapclauses,
best_path->jpath.outerjoinpath->parent->relids,
best_path->jpath.innerjoinpath->parent->relids,
IS_OUTER_JOIN(best_path->jpath.jointype));
}
}
/* Get the join qual clauses (in plain expression form) */
/* Any pseudoconstant clauses are ignored here */
if (IS_OUTER_JOIN(best_path->jpath.jointype))
{
extract_actual_join_clauses(joinrestrictclauses,
&joinclauses, &otherclauses);
}
else
{
/* We can treat all clauses alike for an inner join */
joinclauses = extract_actual_clauses(joinrestrictclauses, false);
otherclauses = NIL;
}
if (best_path->jpath.jointype == JOIN_LASJ_NOTIN)
{
joinclauses = remove_isnotfalse(joinclauses);
}
/* Sort clauses into best execution order */
joinclauses = order_qual_clauses(ctx->root, joinclauses);
otherclauses = order_qual_clauses(ctx->root, otherclauses);
join_plan = make_nestloop(tlist,
joinclauses,
otherclauses,
outer_plan,
inner_plan,
best_path->jpath.jointype);
copy_path_costsize(ctx->root, &join_plan->join.plan, &best_path->jpath.path);
if (IsA(best_path->jpath.innerjoinpath, MaterialPath))
{
MaterialPath *mp = (MaterialPath *)best_path->jpath.innerjoinpath;
if (mp->cdb_strict)
prefetch = true;
}
if (prefetch)
join_plan->join.prefetch_inner = true;
return (Plan *)join_plan;
}
static MergeJoin *
create_mergejoin_plan(CreatePlanContext *ctx,
MergePath *best_path,
Plan *outer_plan,
Plan *inner_plan)
{
List *tlist = build_relation_tlist(best_path->jpath.path.parent);
List *joinclauses;
List *otherclauses;
List *mergeclauses;
Sort *sort;
MergeJoin *join_plan;
bool prefetch=false;
/* Get the join qual clauses (in plain expression form) */
/* Any pseudoconstant clauses are ignored here */
if (IS_OUTER_JOIN(best_path->jpath.jointype))
{
extract_actual_join_clauses(best_path->jpath.joinrestrictinfo,
&joinclauses, &otherclauses);
}
else
{
/* We can treat all clauses alike for an inner join */
joinclauses = extract_actual_clauses(best_path->jpath.joinrestrictinfo,
false);
otherclauses = NIL;
}
/*
* Remove the mergeclauses from the list of join qual clauses, leaving the
* list of quals that must be checked as qpquals.
*/
mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
joinclauses = list_difference(joinclauses, mergeclauses);
/*
* Rearrange mergeclauses, if needed, so that the outer variable is always
* on the left.
*/
mergeclauses =
get_switched_clauses(best_path->jpath.innerjoinpath->parent->relids,
best_path->path_mergeclauses);
/* Sort clauses into best execution order */
/* NB: do NOT reorder the mergeclauses */
joinclauses = order_qual_clauses(ctx->root, joinclauses);
otherclauses = order_qual_clauses(ctx->root, otherclauses);
/*
* Create explicit sort nodes for the outer and inner join paths if
* necessary. The sort cost was already accounted for in the path. Make
* sure there are no excess columns in the inputs if sorting.
*/
if (best_path->outersortkeys)
{
disuse_physical_tlist(outer_plan, best_path->jpath.outerjoinpath);
sort =
make_sort_from_pathkeys(ctx->root,
outer_plan,
best_path->outersortkeys,
best_path->jpath.outerjoinpath->parent->relids,
true);
if (sort)
outer_plan = (Plan *)sort;
}
if (best_path->innersortkeys)
{
disuse_physical_tlist(inner_plan, best_path->jpath.innerjoinpath);
sort =
make_sort_from_pathkeys(ctx->root,
inner_plan,
best_path->innersortkeys,
best_path->jpath.innerjoinpath->parent->relids,
true);
if (sort)
inner_plan = (Plan *)sort;
}
/*
* MPP-3300: very similar to the nested-loop join motion deadlock cases. But we may have already
* put some slackening operators below (e.g. a sort).
*
* We need some kind of strict slackening operator (something which consumes all of its
* input before producing a row of output) for our inner. And we need to prefetch that side
* first.
*/
if (best_path->jpath.outerjoinpath->motionHazard && best_path->jpath.innerjoinpath->motionHazard)
{
prefetch = true;
if (!IsA(inner_plan, Sort))
{
if (IsA(inner_plan, Material))
{
((Material *)inner_plan)->cdb_strict = true;
}
else
{
Material *mat;
/* need to add slack. */
mat = make_material(inner_plan);
mat->cdb_strict = true;
inner_plan = (Plan *)mat;
}
}
}
/*
* Now we can build the mergejoin node.
*/
join_plan = make_mergejoin(tlist,
joinclauses,
otherclauses,
mergeclauses,
outer_plan,
inner_plan,
best_path->jpath.jointype);
join_plan->join.prefetch_inner = prefetch;
copy_path_costsize(ctx->root, &join_plan->join.plan, &best_path->jpath.path);
return join_plan;
}
/*
* Decide if use runtime filter for this hash join.
* If use, three conditions must be satisfied:
* 1. GUC hawq_hashjoin_bloomfilter is enable;
* 2. This hash join is not left outer join or full outer join or anti-join;
* 3. The ratio of (the estimated number of hash join tuples)/(number of tuples of outer table)
* is lower than the GUC hawq_hashjoin_bloomfilter_ratio;
*/
static bool
decide_use_runtime_filter(HashPath* path, JoinType type)
{
if (!hawq_hashjoin_bloomfilter || type == JOIN_LEFT || type == JOIN_FULL
|| type == JOIN_LASJ || type == JOIN_LASJ_NOTIN ||
path->hashjoin_ratio > hawq_hashjoin_bloomfilter_ratio)
{
return false;
}
else
{
return true;
}
}
static HashJoin *
create_hashjoin_plan(CreatePlanContext *ctx,
HashPath *best_path,
Plan *outer_plan,
Plan *inner_plan)
{
List *tlist = build_relation_tlist(best_path->jpath.path.parent);
List *joinclauses;
List *otherclauses;
List *hashclauses;
HashJoin *join_plan;
Hash *hash_plan;
/* Get the join qual clauses (in plain expression form) */
/* Any pseudoconstant clauses are ignored here */
JoinType jointype = best_path->jpath.jointype;
if (IS_OUTER_JOIN(jointype))
{
extract_actual_join_clauses(best_path->jpath.joinrestrictinfo,
&joinclauses, &otherclauses);
}
else
{
/* We can treat all clauses alike for an inner join */
joinclauses = extract_actual_clauses(best_path->jpath.joinrestrictinfo,
false);
otherclauses = NIL;
}
/*
* Remove the hashclauses from the list of join qual clauses, leaving the
* list of quals that must be checked as qpquals.
*/
hashclauses = get_actual_clauses(best_path->path_hashclauses);
joinclauses = list_difference(joinclauses, hashclauses);
/*
* Rearrange hashclauses, if needed, so that the outer variable is always
* on the left.
*/
hashclauses =
get_switched_clauses(best_path->jpath.innerjoinpath->parent->relids,
best_path->path_hashclauses);
/* Sort clauses into best execution order */
joinclauses = order_qual_clauses(ctx->root, joinclauses);
otherclauses = order_qual_clauses(ctx->root, otherclauses);
hashclauses = order_qual_clauses(ctx->root, hashclauses);
/* We don't want any excess columns in the hashed tuples, or in the outer either! */
disuse_physical_tlist(inner_plan, best_path->jpath.innerjoinpath);
if (outer_plan)
disuse_physical_tlist(outer_plan, best_path->jpath.outerjoinpath);
/*
* Build the hash node and hash join node.
*/
hash_plan = make_hash(inner_plan);
join_plan = make_hashjoin(tlist,
joinclauses,
otherclauses,
hashclauses,
NIL, /* hashqualclauses */
outer_plan,
(Plan *) hash_plan,
jointype);
/*
* decide if use runtime filter for this hash join.
*/
join_plan->useRuntimeFilter = decide_use_runtime_filter(best_path, jointype);
/*
* recored the estimated number of rows from inner table,
* this number is used for the memory size of Bloom filter
*/
if (join_plan->useRuntimeFilter)
{
join_plan->estimatedInnerNum = best_path->estimatedNumInner;
}
/*
* MPP-4635. best_path->jpath.outerjoinpath may be NULL.
* From the comment, it is adaptive nestloop join may cause this.
*/
/*
* MPP-4165: we need to descend left-first if *either* of the
* subplans have any motion.
*/
/*
* MPP-3300: unify motion-deadlock prevention for all join types.
* This allows us to undo the MPP-989 changes in nodeHashjoin.c
* (allowing us to check the outer for rows before building the
* hash-table).
*/
if (best_path->jpath.outerjoinpath == NULL ||
best_path->jpath.outerjoinpath->motionHazard ||
best_path->jpath.innerjoinpath->motionHazard)
{
join_plan->join.prefetch_inner = true;
}
copy_path_costsize(ctx->root, &join_plan->join.plan, &best_path->jpath.path);
return join_plan;
}
/*****************************************************************************
*
* SUPPORTING ROUTINES
*
*****************************************************************************/
/*
* fix_indexqual_references
* Adjust indexqual clauses to the form the executor's indexqual
* machinery needs, and check for recheckable (lossy) index conditions.
*
* We have five tasks here:
* * Remove RestrictInfo nodes from the input clauses.
* * Index keys must be represented by Var nodes with varattno set to the
* index's attribute number, not the attribute number in the original rel.
* * If the index key is on the right, commute the clause to put it on the
* left.
* * We must construct lists of operator strategy numbers and subtypes
* for the top-level operators of each index clause.
* * We must detect any lossy index operators. The API is that we return
* a list of the input clauses whose operators are NOT lossy.
*
* fixed_indexquals receives a modified copy of the indexquals list --- the
* original is not changed. Note also that the copy shares no substructure
* with the original; this is needed in case there is a subplan in it (we need
* two separate copies of the subplan tree, or things will go awry).
*
* nonlossy_indexquals receives a list of the original input clauses (with
* RestrictInfos) that contain non-lossy operators.
*
* indexstrategy receives an integer list of strategy numbers.
* indexsubtype receives an OID list of strategy subtypes.
*/
static void
fix_indexqual_references(List *indexquals, IndexPath *index_path,
List **fixed_indexquals,
List **nonlossy_indexquals,
List **indexstrategy,
List **indexsubtype)
{
IndexOptInfo *index = index_path->indexinfo;
ListCell *l;
*fixed_indexquals = NIL;
*nonlossy_indexquals = NIL;
*indexstrategy = NIL;
*indexsubtype = NIL;
/*
* For each qual clause, commute if needed to put the indexkey operand on
* the left, and then fix its varattno. (We do not need to change the
* other side of the clause.) Then determine the operator's strategy
* number and subtype number, and check for lossy index behavior.
*/
foreach(l, indexquals)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
Expr *clause;
Oid clause_op;
Oid opclass = 0;
int stratno;
Oid stratsubtype;
bool recheck;
Assert(IsA(rinfo, RestrictInfo));
/*
* Make a copy that will become the fixed clause.
*
* We used to try to do a shallow copy here, but that fails if there
* is a subplan in the arguments of the opclause. So just do a full
* copy.
*/
clause = (Expr *) copyObject((Node *) rinfo->clause);
if (IsA(clause, OpExpr))
{
OpExpr *op = (OpExpr *) clause;
if (list_length(op->args) != 2)
elog(ERROR, "indexqual clause is not binary opclause");
/*
* Check to see if the indexkey is on the right; if so, commute
* the clause. The indexkey should be the side that refers to
* (only) the base relation.
*/
if (!bms_equal(rinfo->left_relids, index->rel->relids))
CommuteOpExpr(op);
/*
* Now, determine which index attribute this is, change the
* indexkey operand as needed, and get the index opclass.
*/
linitial(op->args) = fix_indexqual_operand(linitial(op->args),
index,
&opclass);
clause_op = op->opno;
}
else if (IsA(clause, RowCompareExpr))
{
RowCompareExpr *rc = (RowCompareExpr *) clause;
ListCell *lc;
/*
* Check to see if the indexkey is on the right; if so, commute
* the clause. The indexkey should be the side that refers to
* (only) the base relation.
*/
if (!bms_overlap(pull_varnos(linitial(rc->largs)),
index->rel->relids))
CommuteRowCompareExpr(rc);
/*
* For each column in the row comparison, determine which index
* attribute this is and change the indexkey operand as needed.
*
* Save the index opclass for only the first column. We will
* return the operator and opclass info for just the first column
* of the row comparison; the executor will have to look up the
* rest if it needs them.
*/
foreach(lc, rc->largs)
{
Oid tmp_opclass;
lfirst(lc) = fix_indexqual_operand(lfirst(lc),
index,
&tmp_opclass);
if (lc == list_head(rc->largs))
opclass = tmp_opclass;
}
clause_op = linitial_oid(rc->opnos);
}
else if (IsA(clause, ScalarArrayOpExpr))
{
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
/* Never need to commute... */
/*
* Now, determine which index attribute this is, change the
* indexkey operand as needed, and get the index opclass.
*/
linitial(saop->args) = fix_indexqual_operand(linitial(saop->args),
index,
&opclass);
clause_op = saop->opno;
}
else
{
elog(ERROR, "unsupported indexqual type: %d",
(int) nodeTag(clause));
continue; /* keep compiler quiet */
}
*fixed_indexquals = lappend(*fixed_indexquals, clause);
/*
* Look up the (possibly commuted) operator in the operator class to
* get its strategy numbers and the recheck indicator. This also
* double-checks that we found an operator matching the index.
*/
get_op_opclass_properties(clause_op, opclass,
&stratno, &stratsubtype, &recheck);
*indexstrategy = lappend_int(*indexstrategy, stratno);
*indexsubtype = lappend_oid(*indexsubtype, stratsubtype);
/* If it's not lossy, add to nonlossy_indexquals */
if (!recheck)
*nonlossy_indexquals = lappend(*nonlossy_indexquals, rinfo);
}
}
static Node *
fix_indexqual_operand(Node *node, IndexOptInfo *index, Oid *opclass)
{
/*
* We represent index keys by Var nodes having the varno of the base table
* but varattno equal to the index's attribute number (index column
* position). This is a bit hokey ... would be cleaner to use a
* special-purpose node type that could not be mistaken for a regular Var.
* But it will do for now.
*/
Var *result;
int pos;
ListCell *indexpr_item;
/*
* Remove any binary-compatible relabeling of the indexkey
*/
if (IsA(node, RelabelType))
node = (Node *) ((RelabelType *) node)->arg;
if (IsA(node, Var) &&
((Var *) node)->varno == index->rel->relid)
{
/* Try to match against simple index columns */
int varatt = ((Var *) node)->varattno;
if (varatt != 0)
{
for (pos = 0; pos < index->ncolumns; pos++)
{
if (index->indexkeys[pos] == varatt)
{
result = (Var *) copyObject(node);
result->varattno = pos + 1;
/* return the correct opclass, too */
*opclass = index->classlist[pos];
return (Node *) result;
}
}
}
}
/* Try to match against index expressions */
indexpr_item = list_head(index->indexprs);
for (pos = 0; pos < index->ncolumns; pos++)
{
if (index->indexkeys[pos] == 0)
{
Node *indexkey;
if (indexpr_item == NULL)
elog(ERROR, "too few entries in indexprs list");
indexkey = (Node *) lfirst(indexpr_item);
if (indexkey && IsA(indexkey, RelabelType))
indexkey = (Node *) ((RelabelType *) indexkey)->arg;
if (equal(node, indexkey))
{
/* Found a match */
result = makeVar(index->rel->relid, pos + 1,
exprType(lfirst(indexpr_item)), -1,
0);
/* return the correct opclass, too */
*opclass = index->classlist[pos];
return (Node *) result;
}
indexpr_item = lnext(indexpr_item);
}
}
/* Ooops... */
elog(ERROR, "node is not an index attribute");
*opclass = InvalidOid; /* keep compiler quiet */
return NULL;
}
/*
* get_switched_clauses
* Given a list of merge or hash joinclauses (as RestrictInfo nodes),
* extract the bare clauses, and rearrange the elements within the
* clauses, if needed, so the outer join variable is on the left and
* the inner is on the right. The original data structure is not touched;
* a modified list is returned.
*
* CDB: Caller specifies inner relids instead of outer relids, because with
* Adaptive NJ there can be HashPlan nodes whose outer relids are not directly
* accessible because outerjoinpath == NULL.
*/
static List *
get_switched_clauses(Relids innerrelids, List *clauses)
{
List *t_list = NIL;
ListCell *l;
foreach(l, clauses)
{
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
Expr *rclause = restrictinfo->clause;
/**
* If this is a IS NOT FALSE boolean test, we can peek underneath.
*/
if (IsA(rclause, BooleanTest))
{
BooleanTest *bt = (BooleanTest *) rclause;
if (bt->booltesttype == IS_NOT_FALSE)
{
rclause = bt->arg;
}
}
Assert(is_opclause(rclause));
OpExpr *clause = (OpExpr *) rclause;
if (bms_is_subset(restrictinfo->left_relids, innerrelids))
{
/*
* Duplicate just enough of the structure to allow commuting the
* clause without changing the original list. Could use
* copyObject, but a complete deep copy is overkill.
*/
OpExpr *temp = makeNode(OpExpr);
temp->opno = clause->opno;
temp->opfuncid = InvalidOid;
temp->opresulttype = clause->opresulttype;
temp->opretset = clause->opretset;
temp->args = list_copy(clause->args);
/* Commute it --- note this modifies the temp node in-place. */
CommuteOpExpr(temp);
t_list = lappend(t_list, temp);
}
else
t_list = lappend(t_list, clause);
}
return t_list;
}
/*
* order_qual_clauses
* Given a list of qual clauses that will all be evaluated at the same
* plan node, sort the list into the order we want to check the quals
* in at runtime.
*
* Ideally the order should be driven by a combination of execution cost and
* selectivity, but unfortunately we have so little information about
* execution cost of operators that it's really hard to do anything smart.
* For now, we just move any quals that contain SubPlan references (but not
* InitPlan references) to the end of the list.
*/
static List *
order_qual_clauses(PlannerInfo *root, List *clauses)
{
List *nosubplans;
List *withsubplans;
ListCell *l;
/* No need to work hard if the query is subselect-free */
if (!root->parse->hasSubLinks)
return clauses;
nosubplans = NIL;
withsubplans = NIL;
foreach(l, clauses)
{
Node *clause = (Node *) lfirst(l);
if (contain_subplans(clause))
withsubplans = lappend(withsubplans, clause);
else
nosubplans = lappend(nosubplans, clause);
}
return list_concat(nosubplans, withsubplans);
}
/*
* Copy cost and size info from a Path node to the Plan node created from it.
* The executor won't use this info, but it's needed by EXPLAIN.
*/
static void
copy_path_costsize(PlannerInfo *root, Plan *dest, Path *src)
{
if (src)
{
dest->startup_cost = src->startup_cost;
dest->total_cost = src->total_cost;
dest->plan_rows = cdbpath_rows(root, src);
dest->plan_width = src->parent->width;
}
else
{
dest->startup_cost = 0;
dest->total_cost = 0;
dest->plan_rows = 0;
dest->plan_width = 0;
}
}
/*
* Copy cost and size info from a lower plan node to an inserted node.
* This is not critical, since the decisions have already been made,
* but it helps produce more reasonable-looking EXPLAIN output.
* (Some callers alter the info after copying it.)
*/
static void
copy_plan_costsize(Plan *dest, Plan *src)
{
if (src)
{
dest->startup_cost = src->startup_cost;
dest->total_cost = src->total_cost;
dest->plan_rows = src->plan_rows;
dest->plan_width = src->plan_width;
}
else
{
dest->startup_cost = 0;
dest->total_cost = 0;
dest->plan_rows = 0;
dest->plan_width = 0;
}
}
/*****************************************************************************
*
* PLAN NODE BUILDING ROUTINES
*
* Some of these are exported because they are called to build plan nodes
* in contexts where we're not deriving the plan node from a path node.
*
*****************************************************************************/
static SeqScan *
make_seqscan(List *qptlist,
List *qpqual,
Index scanrelid)
{
SeqScan *node = makeNode(SeqScan);
Plan *plan = &node->plan;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scanrelid = scanrelid;
return node;
}
static AppendOnlyScan *
make_appendonlyscan(List *qptlist,
List *qpqual,
Index scanrelid)
{
AppendOnlyScan *node = makeNode(AppendOnlyScan);
Plan *plan = &node->scan.plan;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
return node;
}
static ParquetScan *
make_parquetscan(List *qptlist,
List *qpqual,
Index scanrelid)
{
ParquetScan *node = makeNode(ParquetScan);
Plan *plan = &node->scan.plan;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
return node;
}
static ExternalScan *
make_externalscan(List *qptlist,
List *qpqual,
Index scanrelid,
List *urilist,
List *fmtopts,
char fmttype,
bool ismasteronly,
int rejectlimit,
bool rejectlimitinrows,
Oid fmterrtableOid,
int encoding)
{
ExternalScan *node = makeNode(ExternalScan);
Plan *plan = &node->scan.plan;
static uint32 scancounter = 0;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
/* external specifications */
node->uriList = urilist;
node->fmtOpts = fmtopts;
node->fmtType = fmttype;
node->isMasterOnly = ismasteronly;
node->rejLimit = rejectlimit;
node->rejLimitInRows = rejectlimitinrows;
node->fmterrtbl = fmterrtableOid;
node->encoding = encoding;
node->scancounter = scancounter++;
return node;
}
static IndexScan *
make_indexscan(List *qptlist,
List *qpqual,
Index scanrelid,
Oid indexid,
List *indexqual,
List *indexqualorig,
List *indexstrategy,
List *indexsubtype,
ScanDirection indexscandir)
{
IndexScan *node = makeNode(IndexScan);
Plan *plan = &node->scan.plan;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->indexid = indexid;
node->indexqual = indexqual;
node->indexqualorig = indexqualorig;
node->indexstrategy = indexstrategy;
node->indexsubtype = indexsubtype;
node->indexorderdir = indexscandir;
return node;
}
static BitmapIndexScan *
make_bitmap_indexscan(Index scanrelid,
Oid indexid,
List *indexqual,
List *indexqualorig,
List *indexstrategy,
List *indexsubtype)
{
BitmapIndexScan *node = makeNode(BitmapIndexScan);
Plan *plan = &node->scan.plan;
/* cost should be inserted by caller */
plan->targetlist = NIL; /* not used */
plan->qual = NIL; /* not used */
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->indexid = indexid;
node->indexqual = indexqual;
node->indexqualorig = indexqualorig;
node->indexstrategy = indexstrategy;
node->indexsubtype = indexsubtype;
return node;
}
static BitmapHeapScan *
make_bitmap_heapscan(List *qptlist,
List *qpqual,
Plan *lefttree,
List *bitmapqualorig,
Index scanrelid)
{
BitmapHeapScan *node = makeNode(BitmapHeapScan);
Plan *plan = &node->scan.plan;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->bitmapqualorig = bitmapqualorig;
return node;
}
static TidScan *
make_tidscan(List *qptlist,
List *qpqual,
Index scanrelid,
List *tidquals)
{
TidScan *node = makeNode(TidScan);
Plan *plan = &node->scan.plan;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->tidquals = tidquals;
return node;
}
SubqueryScan *
make_subqueryscan(PlannerInfo *root,
List *qptlist,
List *qpqual,
Index scanrelid,
Plan *subplan,
List *subrtable)
{
SubqueryScan *node = makeNode(SubqueryScan);
Plan *plan = &node->scan.plan;
/**
* Ensure that the plan we're going to attach to the subquery scan has all the
* parameter fields figured out.
*/
SS_finalize_plan(root, subrtable, subplan, false);
/*
* Cost is figured here for the convenience of prepunion.c. Note this is
* only correct for the case where qpqual is empty; otherwise caller
* should overwrite cost with a better estimate.
*/
copy_plan_costsize(plan, subplan);
plan->total_cost += cpu_tuple_cost * subplan->plan_rows;
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
plan->extParam = bms_copy(subplan->extParam);
plan->allParam = bms_copy(subplan->allParam);
/*
* Note that, in most scan nodes, scanrelid refers to an entry in the rtable of the
* containing plan; in a subqueryscan node, the containing plan is the higher
* level plan!
*/
node->scan.scanrelid = scanrelid;
node->subplan = subplan;
node->subrtable = subrtable;
return node;
}
static FunctionScan *
make_functionscan(List *qptlist,
List *qpqual,
Index scanrelid)
{
FunctionScan *node = makeNode(FunctionScan);
Plan *plan = &node->scan.plan;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
return node;
}
static ValuesScan *
make_valuesscan(List *qptlist,
List *qpqual,
Index scanrelid)
{
ValuesScan *node = makeNode(ValuesScan);
Plan *plan = &node->scan.plan;
/* cost should be inserted by caller */
plan->targetlist = qptlist;
plan->qual = qpqual;
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
return node;
}
Append *
make_append(List *appendplans, bool isTarget, List *tlist)
{
Append *node = makeNode(Append);
Plan *plan = &node->plan;
ListCell *subnode;
double weighted_total_width = 0.0; /* maintain weighted total */
/*
* Compute cost as sum of subplan costs. We charge nothing extra for the
* Append itself, which perhaps is too optimistic, but since it doesn't do
* any selection or projection, it is a pretty cheap node.
*/
plan->startup_cost = 0;
plan->total_cost = 0;
plan->plan_rows = 0;
plan->plan_width = 0;
foreach(subnode, appendplans)
{
Plan *subplan = (Plan *) lfirst(subnode);
if (subnode == list_head(appendplans))
{
/* first node */
plan->startup_cost = subplan->startup_cost;
}
plan->total_cost += subplan->total_cost;
plan->plan_rows += subplan->plan_rows;
weighted_total_width += (double) subplan->plan_rows * (double) subplan->plan_width;
}
plan->plan_width = (int) ceil(weighted_total_width / Max(1.0,plan->plan_rows));
plan->targetlist = tlist;
plan->qual = NIL;
plan->lefttree = NULL;
plan->righttree = NULL;
node->appendplans = appendplans;
node->isTarget = isTarget;
node->isZapped = false;
node->hasXslice = false;
return node;
}
static BitmapAnd *
make_bitmap_and(List *bitmapplans)
{
BitmapAnd *node = makeNode(BitmapAnd);
Plan *plan = &node->plan;
/* cost should be inserted by caller */
plan->targetlist = NIL;
plan->qual = NIL;
plan->lefttree = NULL;
plan->righttree = NULL;
node->bitmapplans = bitmapplans;
return node;
}
static BitmapOr *
make_bitmap_or(List *bitmapplans)
{
BitmapOr *node = makeNode(BitmapOr);
Plan *plan = &node->plan;
/* cost should be inserted by caller */
plan->targetlist = NIL;
plan->qual = NIL;
plan->lefttree = NULL;
plan->righttree = NULL;
node->bitmapplans = bitmapplans;
return node;
}
NestLoop *
make_nestloop(List *tlist,
List *joinclauses,
List *otherclauses,
Plan *lefttree,
Plan *righttree,
JoinType jointype)
{
NestLoop *node = makeNode(NestLoop);
Plan *plan = &node->join.plan;
/* cost should be inserted by caller */
plan->targetlist = tlist;
plan->qual = otherclauses;
plan->lefttree = lefttree;
plan->righttree = righttree;
node->join.jointype = jointype;
node->join.joinqual = joinclauses;
node->outernotreferencedbyinner = false; /*CDB*/
return node;
}
HashJoin *
make_hashjoin(List *tlist,
List *joinclauses,
List *otherclauses,
List *hashclauses,
List *hashqualclauses,
Plan *lefttree,
Plan *righttree,
JoinType jointype)
{
HashJoin *node = makeNode(HashJoin);
Plan *plan = &node->join.plan;
/* cost should be inserted by caller */
plan->targetlist = tlist;
plan->qual = otherclauses;
plan->lefttree = lefttree;
plan->righttree = righttree;
node->hashclauses = hashclauses;
node->hashqualclauses = hashqualclauses;
node->join.jointype = jointype;
node->join.joinqual = joinclauses;
return node;
}
Hash *
make_hash(Plan *lefttree)
{
Hash *node = makeNode(Hash);
Plan *plan = &node->plan;
copy_plan_costsize(plan, lefttree);
/*
* For plausibility, make startup & total costs equal total cost of input
* plan; this only affects EXPLAIN display not decisions.
*/
plan->startup_cost = plan->total_cost;
plan->targetlist = copyObject(lefttree->targetlist);
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->rescannable = false; /* CDB (unused for now) */
return node;
}
MergeJoin *
make_mergejoin(List *tlist,
List *joinclauses,
List *otherclauses,
List *mergeclauses,
Plan *lefttree,
Plan *righttree,
JoinType jointype)
{
MergeJoin *node = makeNode(MergeJoin);
Plan *plan = &node->join.plan;
/* cost should be inserted by caller */
plan->targetlist = tlist;
plan->qual = otherclauses;
plan->lefttree = lefttree;
plan->righttree = righttree;
node->mergeclauses = mergeclauses;
node->join.jointype = jointype;
node->join.joinqual = joinclauses;
return node;
}
/*
* make_sort --- basic routine to build a Sort plan node
*
* Caller must have built the sortColIdx and sortOperators arrays already.
*/
static Sort *
make_sort(PlannerInfo *root, Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators)
{
Sort *node = makeNode(Sort);
Plan *plan = &node->plan;
copy_plan_costsize(plan, lefttree); /* only care about copying size */
plan = add_sort_cost(root, plan, numCols, sortColIdx, sortOperators);
plan->targetlist = cdbpullup_targetlist(lefttree,
cdbpullup_exprHasSubplanRef((Expr *)lefttree->targetlist));
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->numCols = numCols;
node->sortColIdx = sortColIdx;
node->sortOperators = sortOperators;
node->limitOffset = NULL; /* CDB */
node->limitCount = NULL; /* CDB */
node->noduplicates = false; /* CDB */
node->share_type = SHARE_NOTSHARED;
node->share_id = SHARE_ID_NOT_SHARED;
node->driver_slice = -1;
node->nsharer = 0;
node->nsharer_xslice = 0;
return node;
}
/*
* add_sort_cost --- basic routine to accumulate Sort cost into a
* plan node representing the input cost.
*
* Unused arguments (e.g., sortColIdx and sortOperators arrays) are
* included to allow for future improvements to sort costing. Note
* that root may be NULL (e.g. when called outside make_sort).
*/
Plan *
add_sort_cost(PlannerInfo *root, Plan *input, int numCols, AttrNumber *sortColIdx, Oid *sortOperators)
{
Path sort_path; /* dummy for result of cost_sort */
UnusedArg(numCols);
UnusedArg(sortColIdx);
UnusedArg(sortOperators);
cost_sort(&sort_path, root, NIL,
input->total_cost,
input->plan_rows,
input->plan_width);
input->startup_cost = sort_path.startup_cost;
input->total_cost = sort_path.total_cost;
return input;
}
/*
* add_sort_column --- utility subroutine for building sort info arrays
*
* We need this routine because the same column might be selected more than
* once as a sort key column; if so, the extra mentions are redundant.
*
* Caller is assumed to have allocated the arrays large enough for the
* max possible number of columns. Return value is the new column count.
*/
static int
add_sort_column(AttrNumber colIdx, Oid sortOp,
int numCols, AttrNumber *sortColIdx, Oid *sortOperators)
{
int i;
for (i = 0; i < numCols; i++)
{
if (sortColIdx[i] == colIdx)
{
/* Already sorting by this col, so extra sort key is useless */
return numCols;
}
}
/* Add the column */
sortColIdx[numCols] = colIdx;
sortOperators[numCols] = sortOp;
return numCols + 1;
}
/*
* make_sort_from_pathkeys
* Create sort plan to sort according to given pathkeys
*
* 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted
* 'relids' is the set of relids that can be used in Var nodes here.
* 'add_keys_to_targetlist' is true if it is ok to append to the subplan's
* targetlist or insert a Result node atop the subplan to evaluate
* sort key exprs that are not already present in the subplan's tlist.
*
* We must convert the pathkey information into arrays of sort key column
* numbers and sort operator OIDs.
*
* If the pathkeys include expressions that aren't simple Vars, we will
* usually need to add resjunk items to the input plan's targetlist to
* compute these expressions (since the Sort node itself won't do it).
* If the input plan type isn't one that can do projections, this means
* adding a Result node just to do the projection.
*
* Returns a new Sort node if successful.
*
* Returns NULL if the sort key is degenerate (0 length after truncating
* due to add_keys_to_targetlist==false and/or omitting key cols that are
* equal to a constant expr.)
*/
Sort *
make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
Relids relids, bool add_keys_to_targetlist)
{
List *tlist = lefttree->targetlist;
ListCell *i;
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
/*
* We will need at most list_length(pathkeys) sort columns; possibly less
*/
numsortkeys = list_length(pathkeys);
sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));
sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));
numsortkeys = 0;
foreach(i, pathkeys)
{
List *keysublist = (List *) lfirst(i);
ListCell *j;
PathKeyItem *pathkey = NULL;
TargetEntry *tle = NULL;
AttrNumber resno;
/*
* The column might already be selected as a sort key, if the pathkeys
* contain duplicate entries. (This can happen in scenarios where
* multiple mergejoinable clauses mention the same var, for example.)
* Skip this sort key if it is the same as an earlier one.
*/
foreach(j, pathkeys)
if ((List *)lfirst(j) == keysublist)
break;
if (j != i)
continue;
/*
* We can sort by any one of the sort key items listed in this
* sublist. For now, we take the first one that corresponds to an
* available Var in the tlist. If there isn't any, use the first one
* that is an expression in the input's vars.
*
* XXX if we have a choice, is there any way of figuring out which
* might be cheapest to execute? (For example, int4lt is likely much
* cheaper to execute than numericlt, but both might appear in the
* same pathkey sublist...) Not clear that we ever will have a choice
* in practice, so it may not matter.
*/
pathkey = cdbpullup_findPathKeyItemInTargetList(keysublist,
relids,
tlist,
&resno);
if (!pathkey)
{
/* CDB: Truncate sort keys if caller said don't extend the tlist. */
if (!add_keys_to_targetlist)
break;
elog(ERROR, "could not find pathkey item to sort");
}
/* Omit this sort key if equivalence class contains a constant expr. */
if (pathkey->cdb_num_relids == 0)
continue;
/* If item is not in the tlist, but is computable from tlist vars... */
if (resno == 0)
{
/* CDB: Truncate sort keys if caller said don't extend the tlist. */
if (!add_keys_to_targetlist)
break;
/*
* Do we need to insert a Result node?
*/
if (!is_projection_capable_plan(lefttree))
{
tlist = copyObject(tlist);
lefttree = (Plan *) make_result(tlist, NULL, lefttree);
}
/*
* Add resjunk entry to input's tlist
*/
resno = list_length(tlist) + 1;
tle = makeTargetEntry((Expr *) pathkey->key,
resno,
NULL,
true);
tlist = lappend(tlist, tle);
lefttree->targetlist = tlist; /* just in case NIL before */
}
/* Add column to sort arrays. */
sortColIdx[numsortkeys] = resno;
sortOperators[numsortkeys] = pathkey->sortop;
numsortkeys++;
}
if (numsortkeys == 0)
return NULL;
return make_sort(root, lefttree, numsortkeys,
sortColIdx, sortOperators);
}
/*
* make_sort_from_sortclauses
* Create sort plan to sort according to given sortclauses
*
* 'sortcls' is a list of SortClauses
* 'lefttree' is the node which yields input tuples
*/
Sort *
make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree)
{
List *sub_tlist = lefttree->targetlist;
ListCell *l;
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
/*
* We will need at most list_length(sortcls) sort columns; possibly less
*/
numsortkeys = list_length(sortcls);
sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));
sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));
numsortkeys = 0;
foreach(l, sortcls)
{
SortClause *sortcl = (SortClause *) lfirst(l);
TargetEntry *tle = get_sortgroupclause_tle(sortcl, sub_tlist);
/*
* Check for the possibility of duplicate order-by clauses --- the
* parser should have removed 'em, but no point in sorting
* redundantly.
*/
numsortkeys = add_sort_column(tle->resno, sortcl->sortop,
numsortkeys, sortColIdx, sortOperators);
}
Assert(numsortkeys > 0);
return make_sort(root, lefttree, numsortkeys,
sortColIdx, sortOperators);
}
/*
* make_sort_from_groupcols
* Create sort plan to sort based on grouping columns
*
* 'groupcls' is the list of GroupClauses
* 'grpColIdx' gives the column numbers to use
* 'appendGrouping' represents whether to append a Grouping
* as the last sort key, used for grouping extension.
*
* This might look like it could be merged with make_sort_from_sortclauses,
* but presently we *must* use the grpColIdx[] array to locate sort columns,
* because the child plan's tlist is not marked with ressortgroupref info
* appropriate to the grouping node. So, only the sortop is used from the
* GroupClause entries.
*/
Sort *
make_sort_from_groupcols(PlannerInfo *root,
List *groupcls,
AttrNumber *grpColIdx,
bool appendGrouping,
Plan *lefttree)
{
List *sub_tlist = lefttree->targetlist;
int grpno = 0;
ListCell *l;
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
List *flat_groupcls;
/*
* We will need at most list_length(groupcls) sort columns; possibly less
*/
numsortkeys = num_distcols_in_grouplist(groupcls);
if (appendGrouping)
numsortkeys ++;
sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));
sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));
numsortkeys = 0;
flat_groupcls = flatten_grouping_list(groupcls);
foreach(l, flat_groupcls)
{
GroupClause *grpcl = (GroupClause *) lfirst(l);
TargetEntry *tle = get_tle_by_resno(sub_tlist, grpColIdx[grpno]);
grpno++;
/*
* Check for the possibility of duplicate group-by clauses --- the
* parser should have removed 'em, but no point in sorting
* redundantly.
*/
numsortkeys = add_sort_column(tle->resno, grpcl->sortop,
numsortkeys, sortColIdx, sortOperators);
}
if (appendGrouping)
{
Oid sort_op;
/* Grouping will be the last entry in grpColIdx */
TargetEntry *tle = get_tle_by_resno(sub_tlist, grpColIdx[grpno]);
if (tle->resname == NULL)
tle->resname = "grouping";
sort_op = ordering_oper_opid(exprType((Node *)tle->expr));
numsortkeys = add_sort_column(tle->resno, sort_op,
numsortkeys, sortColIdx, sortOperators);
}
Assert(numsortkeys > 0);
return make_sort(root, lefttree, numsortkeys,
sortColIdx, sortOperators);
}
/*
* make_sort_from_reordered_groupcols
* Create sort plan to sort based on re-ordered grouping columns.
*
* 'groupcls' is the list of GroupClauses
* 'orig_groupColIdx' is the original columns numbers for groupcls
* 'new_grpColIdx' gives the re-ordered column numbers to use
* 'grouping' represents the target entry for the Grouping column.
* If this value is not NULL, "Grouping" column will be added
* into the sorting list.
*/
Sort*
make_sort_from_reordered_groupcols(PlannerInfo *root,
List *groupcls,
AttrNumber *orig_grpColIdx,
AttrNumber *new_grpColIdx,
TargetEntry *grouping,
TargetEntry *groupid,
int req_ngrpkeys,
Plan *lefttree)
{
List *sub_tlist = lefttree->targetlist;
int grpno = 0;
int numgrpkeys, numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
List *flat_groupcls;
/*
* We will need at most list_length(groupcls) sort columns; possibly less
*/
numgrpkeys = num_distcols_in_grouplist(groupcls);
Assert(req_ngrpkeys <= numgrpkeys);
if (grouping != NULL)
{
Assert(groupid != NULL);
sortColIdx = (AttrNumber *) palloc((numgrpkeys + 2) * sizeof(AttrNumber));
sortOperators = (Oid *) palloc((numgrpkeys + 2) * sizeof(Oid));
}
else
{
sortColIdx = (AttrNumber *) palloc(numgrpkeys * sizeof(AttrNumber));
sortOperators = (Oid *) palloc(numgrpkeys * sizeof(Oid));
}
numsortkeys = 0;
flat_groupcls = flatten_grouping_list(groupcls);
for (grpno=0; grpno < req_ngrpkeys; grpno++)
{
GroupClause *grpcl;
TargetEntry *tle;
int pos;
/* Find the index position in orig_grpColIdx, in which
* the element is equal to new_grpColIdx[grpno]. This index
* position points to the place that the corresponding
* GroupClause in flat_groupcls.
*/
for (pos=0; pos < numgrpkeys; pos++)
{
if (orig_grpColIdx[pos] == new_grpColIdx[grpno])
break;
}
Assert (pos < numgrpkeys);
grpcl = (GroupClause *)list_nth(flat_groupcls, pos);
tle = get_tle_by_resno(sub_tlist, new_grpColIdx[grpno]);
/*
* Check for the possibility of duplicate group-by clauses --- the
* parser should have removed 'em, but no point in sorting
* redundantly.
*/
numsortkeys = add_sort_column(tle->resno, grpcl->sortop,
numsortkeys, sortColIdx, sortOperators);
}
if (grouping != NULL)
{
Oid sort_op = ordering_oper_opid(exprType((Node *)grouping->expr));
numsortkeys = add_sort_column(grouping->resno, sort_op,
numsortkeys, sortColIdx, sortOperators);
sort_op = ordering_oper_opid(exprType((Node *)groupid->expr));
numsortkeys = add_sort_column(groupid->resno, sort_op,
numsortkeys, sortColIdx, sortOperators);
}
Assert(numsortkeys > 0);
return make_sort(root, lefttree, numsortkeys,
sortColIdx, sortOperators);
}
/*
* Reconstruct a new list of GroupClause based on the given grpCols.
*
* The original grouping clauses may contain grouping extensions. This function
* extract the raw grouping attributes and construct a list of GroupClauses
* that contains only ordinary grouping.
*/
List *
reconstruct_group_clause(List *orig_groupClause, List *tlist, AttrNumber *grpColIdx, int numcols)
{
List *flat_groupcls;
List *new_groupClause = NIL;
int numgrpkeys;
int grpno;
numgrpkeys = num_distcols_in_grouplist(orig_groupClause);
flat_groupcls = flatten_grouping_list(orig_groupClause);
for (grpno = 0; grpno < numcols; grpno++)
{
ListCell *lc = NULL;
TargetEntry *te;
GroupClause *gc = NULL;
te = get_tle_by_resno(tlist, grpColIdx[grpno]);
foreach (lc, flat_groupcls)
{
gc = (GroupClause *)lfirst(lc);
if (gc->tleSortGroupRef == te->ressortgroupref)
break;
}
if (lc != NULL)
new_groupClause = lappend(new_groupClause, gc);
}
return new_groupClause;
}
Material *
make_material(Plan *lefttree)
{
Material *node = makeNode(Material);
Plan *plan = &node->plan;
/* cost should be inserted by caller */
plan->targetlist = copyObject(lefttree->targetlist);
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->cdb_strict = false;
node->share_type = SHARE_NOTSHARED;
node->share_id = SHARE_ID_NOT_SHARED;
node->driver_slice = -1;
node->nsharer = 0;
node->nsharer_xslice= 0;
return node;
}
/*
* materialize_finished_plan: stick a Material node atop a completed plan
*
* There are a couple of places where we want to attach a Material node
* after completion of subquery_planner(). This currently requires hackery.
* Since subquery_planner has already run SS_finalize_plan on the subplan
* tree, we have to kluge up parameter lists for the Material node.
* Possibly this could be fixed by postponing SS_finalize_plan processing
* until setrefs.c is run?
*/
Plan *
materialize_finished_plan(PlannerInfo *root, Plan *subplan)
{
Plan *matplan;
Path matpath; /* dummy for result of cost_material */
matplan = (Plan *) make_material(subplan);
/* Set cost data */
cost_material(&matpath,
root,
subplan->total_cost,
subplan->plan_rows,
subplan->plan_width);
matplan->startup_cost = matpath.startup_cost;
matplan->total_cost = matpath.total_cost;
matplan->plan_rows = subplan->plan_rows;
matplan->plan_width = subplan->plan_width;
/* MPP -- propagate dispatch method and flow */
matplan->dispatch = subplan->dispatch;
matplan->flow = copyObject(subplan->flow);
/* parameter kluge --- see comments above */
matplan->extParam = bms_copy(subplan->extParam);
matplan->allParam = bms_copy(subplan->allParam);
return matplan;
}
Agg *
make_agg(PlannerInfo *root, List *tlist, List *qual,
AggStrategy aggstrategy, bool streaming,
int numGroupCols, AttrNumber *grpColIdx,
long numGroups, int num_nullcols,
uint64 input_grouping, uint64 grouping,
int rollupGSTimes,
int numAggs, int transSpace,
Plan *lefttree)
{
Agg *node = makeNode(Agg);
Plan *plan = &node->plan;
node->aggstrategy = aggstrategy;
node->numCols = numGroupCols;
node->grpColIdx = grpColIdx;
node->numGroups = numGroups;
node->transSpace = transSpace;
node->numNullCols = num_nullcols;
node->inputGrouping = input_grouping;
node->grouping = grouping;
node->inputHasGrouping = false;
node->rollupGSTimes = rollupGSTimes;
node->lastAgg = false;
node->streaming = streaming;
copy_plan_costsize(plan, lefttree); /* only care about copying size */
add_agg_cost(root, plan, tlist, qual, aggstrategy, streaming,
numGroupCols, grpColIdx,
numGroups, num_nullcols,
numAggs, transSpace);
plan->qual = qual;
plan->targetlist = tlist;
plan->lefttree = lefttree;
plan->righttree = NULL;
plan->extParam = bms_copy(lefttree->extParam);
plan->allParam = bms_copy(lefttree->allParam);
return node;
}
/* add_agg_cost -- basic routine to accumulate Agg cost into a
* plan node representing the input cost.
*
* Unused arguments (e.g., streaming, grpColIdx, num_nullcols)
* are included to allow for future improvements to aggregate
* costing. Note that root may be NULL (e.g., when called from
* outside make_agg).
*/
Plan *add_agg_cost(PlannerInfo *root, Plan *plan,
List *tlist, List *qual,
AggStrategy aggstrategy,
bool streaming,
int numGroupCols, AttrNumber *grpColIdx,
long numGroups, int num_nullcols,
int numAggs, int transSpace)
{
Path agg_path; /* dummy for result of cost_agg */
QualCost qual_cost;
HashAggTableSizes hash_info;
UnusedArg(grpColIdx);
UnusedArg(num_nullcols);
/* Solution for MPP-11942
* Before this fix, we calculated the width from the sub_tlist which
* only contains a subset of the tlist. For example, for the query
* select a, min(b), max(b), min(c), max(c) from s group by a;
* the sub_tlist has the columns {a,b,c} while the tlist
* is {a, min(b), max(b), min(c), max(c)}. Therefore, the plan_width
* for the above query is calculated to be 12 instead of 20.
*
* In this fix, we calculate the actual row width from the tlist.
*/
plan->plan_width = get_row_width(tlist);
if (aggstrategy == AGG_HASHED)
{
if (!calcHashAggTableSizes(global_work_mem(root),
numGroups,
numAggs,
/* The following estimate is very rough but good enough for planning. */
sizeof(HeapTupleData) + sizeof(HeapTupleHeaderData) + plan->plan_width,
transSpace,
true,
&hash_info))
{
elog(ERROR, "Planner committed to impossible hash aggregate.");
}
cost_agg(&agg_path, root,
aggstrategy, numAggs,
numGroupCols, numGroups,
plan->startup_cost,
plan->total_cost,
plan->plan_rows, hash_info.workmem_per_entry,
hash_info.nbatches, hash_info.hashentry_width, streaming);
}
else
cost_agg(&agg_path, root,
aggstrategy, numAggs,
numGroupCols, numGroups,
plan->startup_cost,
plan->total_cost,
plan->plan_rows, 0.0, 0.0,
0.0, false);
plan->startup_cost = agg_path.startup_cost;
plan->total_cost = agg_path.total_cost;
/*
* We will produce a single output tuple if not grouping, and a tuple per
* group otherwise.
*/
if (aggstrategy == AGG_PLAIN)
plan->plan_rows = 1;
else
plan->plan_rows = numGroups;
/*
* We also need to account for the cost of evaluation of the qual (ie, the
* HAVING clause) and the tlist. Note that cost_qual_eval doesn't charge
* anything for Aggref nodes; this is okay since they are really
* comparable to Vars.
*
* See notes in grouping_planner about why this routine and make_group are
* the only ones in this file that worry about tlist eval cost.
*/
if (qual)
{
cost_qual_eval(&qual_cost, qual, root);
plan->startup_cost += qual_cost.startup;
plan->total_cost += qual_cost.startup;
plan->total_cost += qual_cost.per_tuple * plan->plan_rows;
}
cost_qual_eval(&qual_cost, tlist, root);
plan->startup_cost += qual_cost.startup;
plan->total_cost += qual_cost.startup;
plan->total_cost += qual_cost.per_tuple * plan->plan_rows;
return plan;
}
Window *make_window(PlannerInfo *root, List *tlist,
int numPartCols, AttrNumber *partColIdx, List *windowKeys,
Plan *lefttree)
{
Window *node = makeNode(Window);
Plan *plan = &node->plan;
Path window_path; /* dummy for result of cost_window */
QualCost qual_cost;
int numOrderCols;
ListCell *cell;
node->numPartCols = numPartCols;
node->partColIdx = partColIdx;
node->windowKeys = windowKeys;
numOrderCols = numPartCols;
foreach(cell, windowKeys)
{
/* TODO Shouldn't we copy? */
WindowKey *key = (WindowKey *)lfirst(cell);
numOrderCols += key->numSortCols;
}
copy_plan_costsize(plan, lefttree); /* only care about copying size */
cost_window(&window_path, root,
numOrderCols,
lefttree->startup_cost,
lefttree->total_cost,
lefttree->plan_rows);
plan->startup_cost = window_path.startup_cost;
plan->total_cost = window_path.total_cost;
cost_qual_eval(&qual_cost, tlist, root);
plan->startup_cost += qual_cost.startup;
plan->total_cost += qual_cost.startup;
plan->total_cost += qual_cost.per_tuple * plan->plan_rows;
plan->qual = NIL;
plan->targetlist = tlist;
plan->lefttree = lefttree;
plan->righttree = NULL;
return node;
}
static TableFunctionScan*
make_tablefunction(List *tlist,
List *scan_quals,
Plan *subplan,
List *subrtable,
Index scanrelid)
{
TableFunctionScan *node = makeNode(TableFunctionScan);
Plan *plan = &node->scan.plan;
copy_plan_costsize(plan, subplan); /* only care about copying size */
/* FIXME: fix costing */
plan->startup_cost = subplan->startup_cost;
plan->total_cost = subplan->total_cost;
plan->total_cost += 2 * plan->plan_rows;
plan->qual = scan_quals;
plan->targetlist = tlist;
plan->righttree = NULL;
/* Fill in information for the subplan */
plan->lefttree = subplan;
node->subrtable = subrtable;
node->scan.scanrelid = scanrelid;
return node;
}
/*
* distinctList is a list of SortClauses, identifying the targetlist items
* that should be considered by the Unique filter.
*/
Unique *
make_unique(Plan *lefttree, List *distinctList)
{
Unique *node = makeNode(Unique);
Plan *plan = &node->plan;
int numCols = list_length(distinctList);
int keyno = 0;
AttrNumber *uniqColIdx;
ListCell *slitem;
copy_plan_costsize(plan, lefttree);
/*
* Charge one cpu_operator_cost per comparison per input tuple. We assume
* all columns get compared at most of the tuples. (XXX probably this is
* an overestimate.)
*/
plan->total_cost += cpu_operator_cost * plan->plan_rows * numCols;
/*
* plan->plan_rows is left as a copy of the input subplan's plan_rows; ie,
* we assume the filter removes nothing. The caller must alter this if he
* has a better idea.
*/
plan->targetlist = cdbpullup_targetlist(lefttree,
cdbpullup_exprHasSubplanRef((Expr *)lefttree->targetlist));
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
/*
* convert SortClause list into array of attr indexes, as wanted by exec
*/
Assert(numCols > 0);
uniqColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
foreach(slitem, distinctList)
{
SortClause *sortcl = (SortClause *) lfirst(slitem);
TargetEntry *tle = get_sortgroupclause_tle(sortcl, plan->targetlist);
uniqColIdx[keyno++] = tle->resno;
}
node->numCols = numCols;
node->uniqColIdx = uniqColIdx;
/* CDB */ /* pass DISTINCT to sort */
if (IsA(lefttree, Sort) && gp_enable_sort_distinct)
{
Sort* pSort = (Sort*)lefttree;
pSort->noduplicates = true;
}
return node;
}
/*
* distinctList is a list of SortClauses, identifying the targetlist items
* that should be considered by the SetOp filter.
*/
SetOp *
make_setop(SetOpCmd cmd, Plan *lefttree,
List *distinctList, AttrNumber flagColIdx)
{
SetOp *node = makeNode(SetOp);
Plan *plan = &node->plan;
int numCols = list_length(distinctList);
int keyno = 0;
AttrNumber *dupColIdx;
ListCell *slitem;
copy_plan_costsize(plan, lefttree);
/*
* Charge one cpu_operator_cost per comparison per input tuple. We assume
* all columns get compared at most of the tuples.
*/
plan->total_cost += cpu_operator_cost * plan->plan_rows * numCols;
/*
* We make the unsupported assumption that there will be 10% as many
* tuples out as in. Any way to do better?
*/
plan->plan_rows *= 0.1;
if (plan->plan_rows < 1)
plan->plan_rows = 1;
plan->targetlist = cdbpullup_targetlist(lefttree,
cdbpullup_exprHasSubplanRef((Expr *)lefttree->targetlist));
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
/*
* convert SortClause list into array of attr indexes, as wanted by exec
*/
Assert(numCols > 0);
dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
foreach(slitem, distinctList)
{
SortClause *sortcl = (SortClause *) lfirst(slitem);
TargetEntry *tle = get_sortgroupclause_tle(sortcl, plan->targetlist);
dupColIdx[keyno++] = tle->resno;
}
node->cmd = cmd;
node->numCols = numCols;
node->dupColIdx = dupColIdx;
node->flagColIdx = flagColIdx;
return node;
}
/*
* Note: offset_est and count_est are passed in to save having to repeat
* work already done to estimate the values of the limitOffset and limitCount
* expressions. Their values are as returned by preprocess_limit (0 means
* "not relevant", -1 means "couldn't estimate"). Keep the code below in sync
* with that function!
*/
Limit *
make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount,
int64 offset_est, int64 count_est)
{
Limit *node = makeNode(Limit);
Plan *plan = &node->plan;
copy_plan_costsize(plan, lefttree);
/*
* Adjust the output rows count and costs according to the offset/limit.
* This is only a cosmetic issue if we are at top level, but if we are
* building a subquery then it's important to report correct info to the
* outer planner.
*
* When the offset or count couldn't be estimated, use 10% of the
* estimated number of rows emitted from the subplan.
*/
if (offset_est != 0)
{
double offset_rows;
if (offset_est > 0)
offset_rows = (double) offset_est;
else
offset_rows = clamp_row_est(lefttree->plan_rows * 0.10);
if (offset_rows > plan->plan_rows)
offset_rows = plan->plan_rows;
if (plan->plan_rows > 0)
plan->startup_cost +=
(plan->total_cost - plan->startup_cost)
* offset_rows / plan->plan_rows;
plan->plan_rows -= offset_rows;
if (plan->plan_rows < 1)
plan->plan_rows = 1;
}
if (count_est != 0)
{
double count_rows;
if (count_est > 0)
count_rows = (double) count_est;
else
count_rows = clamp_row_est(lefttree->plan_rows * 0.10);
if (count_rows > plan->plan_rows)
count_rows = plan->plan_rows;
if (plan->plan_rows > 0)
plan->total_cost = plan->startup_cost +
(plan->total_cost - plan->startup_cost)
* count_rows / plan->plan_rows;
plan->plan_rows = count_rows;
if (plan->plan_rows < 1)
plan->plan_rows = 1;
}
plan->targetlist = cdbpullup_targetlist(lefttree,
cdbpullup_exprHasSubplanRef((Expr *)lefttree->targetlist));
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
node->limitOffset = limitOffset;
node->limitCount = limitCount;
/* CDB */ /* pass limit struct to sort */
if (IsA(lefttree, Sort) && gp_enable_sort_limit)
{
Sort* pSort = (Sort*)lefttree;
pSort->limitOffset = copyObject(limitOffset);
pSort->limitCount = copyObject(limitCount);
}
return node;
}
/*
* make_result
* Build a Result plan node
*
* If we have a subplan, assume that any evaluation costs for the gating qual
* were already factored into the subplan's startup cost, and just copy the
* subplan cost. If there's no subplan, we should include the qual eval
* cost. In either case, tlist eval cost is not to be included here.
*/
Result *
make_result(/*PlannerInfo *root,*/
List *tlist,
Node *resconstantqual,
Plan *subplan)
{
Result *node = makeNode(Result);
Plan *plan = &node->plan;
if (subplan)
copy_plan_costsize(plan, subplan);
else
{
plan->startup_cost = 0;
plan->total_cost = cpu_tuple_cost;
plan->plan_rows = 1; /* wrong if we have a set-valued function? */
plan->plan_width = 0; /* XXX is it worth being smarter? */
if (resconstantqual)
{
QualCost qual_cost;
cost_qual_eval(&qual_cost, (List *) resconstantqual, NULL /*root*/);
/* resconstantqual is evaluated once at startup */
plan->startup_cost += qual_cost.startup + qual_cost.per_tuple;
plan->total_cost += qual_cost.startup + qual_cost.per_tuple;
}
}
plan->targetlist = tlist;
plan->qual = NIL;
plan->lefttree = subplan;
plan->righttree = NULL;
node->resconstantqual = resconstantqual;
node->hashFilter = false;
node->hashList = NIL;
return node;
}
/*
* make_repeat
* Build a Repeat plan node
*/
Repeat *
make_repeat(List *tlist,
List *qual,
Expr *repeatCountExpr,
uint64 grouping,
Plan *subplan)
{
Repeat *node = makeNode(Repeat);
Plan *plan = &node->plan;
Assert(subplan != NULL);
copy_plan_costsize(plan, subplan);
plan->targetlist = tlist;
plan->qual = qual;
plan->lefttree = subplan;
plan->righttree = NULL;
node->repeatCountExpr = repeatCountExpr;
node->grouping = grouping;
return node;
}
/*
* is_projection_capable_plan
* Check whether a given Plan node is able to do projection.
*/
bool
is_projection_capable_plan(Plan *plan)
{
/* Most plan types can project, so just list the ones that can't */
switch (nodeTag(plan))
{
case T_Hash:
case T_Material:
case T_Sort:
case T_Unique:
case T_SetOp:
case T_Limit:
case T_Append:
case T_Motion:
case T_ShareInputScan:
return false;
default:
break;
}
return true;
}
/*
* plan_pushdown_tlist
*
* If the given Plan node does projection, the same node is returned after
* replacing its targetlist with the given targetlist.
*
* Otherwise, returns a Result node with the given targetlist, inserted atop
* the given plan.
*/
Plan *
plan_pushdown_tlist(Plan *plan, List *tlist)
{
if (is_projection_capable_plan(plan))
{
/* Fix up annotation of plan's distribution and ordering properties. */
if (plan->flow)
plan->flow = pull_up_Flow((Plan *)make_result(tlist, NULL, plan),
plan, true);
/* Install the new targetlist. */
plan->targetlist = tlist;
}
else
{
Plan *subplan = plan;
/* Insert a Result node to evaluate the targetlist. */
plan = (Plan *)make_result(tlist, NULL, subplan);
/* Propagate the subplan's distribution and ordering properties. */
if (subplan->flow)
plan->flow = pull_up_Flow(plan, subplan, true);
}
return plan;
} /* plan_pushdown_tlist */
/*
* Return true if there is the same tleSortGroupRef in an entry in glist
* as the tleSortGroupRef in gc.
*/
static bool
groupcol_in_list(GroupClause *gc, List *glist)
{
bool found = false;
ListCell *lc;
foreach (lc, glist)
{
GroupClause *entry = (GroupClause *)lfirst(lc);
Assert (IsA(entry, GroupClause));
if (gc->tleSortGroupRef == entry->tleSortGroupRef)
{
found = true;
break;
}
}
return found;
}
/*
* Construct a list of GroupClauses from the transformed GROUP BY clause.
* This list of GroupClauses has unique tleSortGroupRefs.
*/
static List *
flatten_grouping_list(List *groupcls)
{
List *result = NIL;
ListCell *gc;
foreach (gc, groupcls)
{
Node *node = (Node*)lfirst(gc);
if (node == NULL)
continue;
Assert(IsA(node, GroupingClause) ||
IsA(node, GroupClause) ||
IsA(node, List));
if (IsA(node, GroupingClause))
{
List *groupsets = ((GroupingClause*)node)->groupsets;
List *flatten_groupsets =
flatten_grouping_list(groupsets);
ListCell *lc;
foreach (lc, flatten_groupsets)
{
GroupClause *flatten_gc = (GroupClause *)lfirst(lc);
Assert(IsA(flatten_gc, GroupClause));
if (!groupcol_in_list(flatten_gc, result))
result = lappend(result, flatten_gc);
}
}
else if (IsA(node, List))
{
List *flatten_groupsets =
flatten_grouping_list((List *)node);
ListCell *lc;
foreach (lc, flatten_groupsets)
{
GroupClause *flatten_gc = (GroupClause *)lfirst(lc);
Assert(IsA(flatten_gc, GroupClause));
if (!groupcol_in_list(flatten_gc, result))
result = lappend(result, flatten_gc);
}
}
else
{
Assert(IsA(node, GroupClause));
if (!groupcol_in_list((GroupClause *)node, result))
result = lappend(result, node);
}
}
return result;
}