forked from huawei/openGauss-server
9047 lines
339 KiB
C++
Executable File
9047 lines
339 KiB
C++
Executable File
/* -------------------------------------------------------------------------
|
|
*
|
|
* pathnode.cpp
|
|
* Routines to manipulate pathlists and create path nodes
|
|
*
|
|
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/gausskernel/optimizer/util/pathnode.cpp
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include "bulkload/foreignroutine.h"
|
|
#include "catalog/pg_statistic.h"
|
|
#include "commands/copy.h"
|
|
#include "foreign/foreign.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "nodes/print.h"
|
|
#include "nodes/relation.h"
|
|
#include "optimizer/clauses.h"
|
|
#include "optimizer/cost.h"
|
|
#include "optimizer/dataskew.h"
|
|
#include "optimizer/nodegroups.h"
|
|
#include "optimizer/optimizerdebug.h"
|
|
#include "optimizer/planmain.h"
|
|
#include "optimizer/planner.h"
|
|
#include "optimizer/pathnode.h"
|
|
#include "optimizer/paths.h"
|
|
#include "optimizer/planmain.h"
|
|
#include "optimizer/planner.h"
|
|
#include "optimizer/pruning.h"
|
|
#include "optimizer/randomplan.h"
|
|
#include "optimizer/restrictinfo.h"
|
|
#include "optimizer/var.h"
|
|
#include "parser/parse_hint.h"
|
|
#include "parser/parsetree.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/syscache.h"
|
|
#include "utils/selfuncs.h"
|
|
#ifdef PGXC
|
|
#include "commands/tablecmds.h"
|
|
#include "optimizer/restrictinfo.h"
|
|
#include "optimizer/streamplan.h"
|
|
#include "pgxc/pgxc.h"
|
|
#endif /* PGXC */
|
|
|
|
static bool is_itst_path(PlannerInfo* root, RelOptInfo* rel, Path* path);
|
|
static void add_parameterized_path(RelOptInfo* parent_rel, Path* new_path);
|
|
static List* translate_sub_tlist(List* tlist, int relid);
|
|
static bool check_join_method_alternative(
|
|
List* restrictlist, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinType jointype, bool* try_eq_related_indirectly);
|
|
|
|
#ifdef STREAMPLAN
|
|
static void mark_append_path(PlannerInfo* root, RelOptInfo* rel, Path* pathnode, List* subpaths);
|
|
static bool is_ec_usable_for_join(
|
|
Relids suitable_relids, EquivalenceClass* suitable_ec, Node* diskey, Expr* join_clause, bool is_left);
|
|
static bool is_diskey_and_joinkey_compatible(Node* diskey, Node* joinkey);
|
|
static List* get_otherside_key(
|
|
PlannerInfo* root, List* rinfo, List* targetlist, RelOptInfo* otherside_rel, double* skew_multiple);
|
|
static void add_hashjoin_broadcast_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype,
|
|
JoinType save_jointype, JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors,
|
|
Path* need_stream_path, Path* non_stream_path, List* restrictlist, Relids required_outer, List* hashclauses,
|
|
bool is_replicate, bool stream_outer, Distribution* target_distribution = NULL, ParallelDesc* need_smpDesc = NULL,
|
|
ParallelDesc* non_smpDesc = NULL, int dop = 1);
|
|
static void add_nestloop_broadcast_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype,
|
|
JoinType save_jointype, JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors,
|
|
Path* need_stream_path, Path* non_stream_path, List* restrict_clauses, List* pathkeys, Relids required_outer,
|
|
List* stream_pathkeys, bool is_replicate, bool stream_outer, Distribution* target_distribution = NULL,
|
|
ParallelDesc* need_smpDesc = NULL, ParallelDesc* non_smpDesc = NULL, int dop = 1);
|
|
static void add_mergejoin_broadcast_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype,
|
|
JoinType save_jointype, JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, Path* need_stream_path,
|
|
Path* non_stream_path, List* restrict_clauses, List* pathkeys, Relids required_outer, List* mergeclauses,
|
|
List* outersortkeys, List* innersortkeys, List* stream_pathkeys, List* non_stream_pathkeys, bool is_replicate,
|
|
bool stream_outer, Distribution* target_distribution = NULL);
|
|
#endif
|
|
|
|
/*****************************************************************************
|
|
* MISC. PATH UTILITIES
|
|
*****************************************************************************/
|
|
/*
|
|
* compare_path_costs
|
|
* Return -1, 0, or +1 according as path1 is cheaper, the same cost,
|
|
* or more expensive than path2 for the specified criterion.
|
|
*/
|
|
int compare_path_costs(Path* path1, Path* path2, CostSelector criterion)
|
|
{
|
|
if (criterion == STARTUP_COST) {
|
|
if (path1->startup_cost < path2->startup_cost)
|
|
return -1;
|
|
if (path1->startup_cost > path2->startup_cost)
|
|
return +1;
|
|
|
|
/*
|
|
* If paths have the same startup cost (not at all unlikely), order
|
|
* them by total cost.
|
|
*/
|
|
if (path1->total_cost < path2->total_cost)
|
|
return -1;
|
|
if (path1->total_cost > path2->total_cost)
|
|
return +1;
|
|
} else {
|
|
if (path1->total_cost < path2->total_cost)
|
|
return -1;
|
|
if (path1->total_cost > path2->total_cost)
|
|
return +1;
|
|
|
|
/*
|
|
* If paths have the same total cost, order them by startup cost.
|
|
*/
|
|
if (path1->startup_cost < path2->startup_cost)
|
|
return -1;
|
|
if (path1->startup_cost > path2->startup_cost)
|
|
return +1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* compare_path_fractional_costs
|
|
* Return -1, 0, or +1 according as path1 is cheaper, the same cost,
|
|
* or more expensive than path2 for fetching the specified fraction
|
|
* of the total tuples.
|
|
*
|
|
* If fraction is <= 0 or > 1, we interpret it as 1, ie, we select the
|
|
* path with the cheaper total_cost.
|
|
*/
|
|
int compare_fractional_path_costs(Path* path1, Path* path2, double fraction)
|
|
{
|
|
Cost cost1, cost2;
|
|
|
|
if (fraction <= 0.0 || fraction >= 1.0)
|
|
return compare_path_costs(path1, path2, TOTAL_COST);
|
|
cost1 = path1->startup_cost + fraction * (path1->total_cost - path1->startup_cost);
|
|
cost2 = path2->startup_cost + fraction * (path2->total_cost - path2->startup_cost);
|
|
if (cost1 < cost2)
|
|
return -1;
|
|
if (cost1 > cost2)
|
|
return +1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* compare_path_costs_fuzzily
|
|
* Compare the costs of two paths to see if either can be said to
|
|
* dominate the other.
|
|
*
|
|
* We use fuzzy comparisons so that add_path() can avoid keeping both of
|
|
* a pair of paths that really have insignificantly different cost.
|
|
*
|
|
* The fuzz_factor argument must be 1.0 plus delta, where delta is the
|
|
* fraction of the smaller cost that is considered to be a significant
|
|
* difference. For example, fuzz_factor = 1.01 makes the fuzziness limit
|
|
* be 1% of the smaller cost.
|
|
*
|
|
* The two paths are said to have "equal" costs if both startup and total
|
|
* costs are fuzzily the same. Path1 is said to be better than path2 if
|
|
* it has fuzzily better startup cost and fuzzily no worse total cost,
|
|
* or if it has fuzzily better total cost and fuzzily no worse startup cost.
|
|
* Path2 is better than path1 if the reverse holds. Finally, if one path
|
|
* is fuzzily better than the other on startup cost and fuzzily worse on
|
|
* total cost, we just say that their costs are "different", since neither
|
|
* dominates the other across the whole performance spectrum.
|
|
*/
|
|
PathCostComparison compare_path_costs_fuzzily(Path* path1, Path* path2, double fuzz_factor)
|
|
{
|
|
if (path1->hint_value > path2->hint_value)
|
|
return COSTS_BETTER1;
|
|
else if (path1->hint_value < path2->hint_value)
|
|
return COSTS_BETTER2;
|
|
|
|
/*
|
|
* Check total cost first since it's more likely to be different; many
|
|
* paths have zero startup cost.
|
|
*/
|
|
if (fuzz_factor - SMALL_FUZZY_FACTOR == 0) {
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
(errmsg("SMALL_FUZZY_FACTOR is used to compare %lf .. %lf v.s. %lf .. % lf",
|
|
path1->startup_cost,
|
|
path1->total_cost,
|
|
path2->startup_cost,
|
|
path2->total_cost))));
|
|
}
|
|
if (path1->total_cost > path2->total_cost * fuzz_factor) {
|
|
/* path1 fuzzily worse on total cost */
|
|
if (path2->startup_cost > path1->startup_cost * fuzz_factor) {
|
|
/* ... but path2 fuzzily worse on startup, so DIFFERENT */
|
|
return COSTS_DIFFERENT;
|
|
}
|
|
/* else path2 dominates */
|
|
return COSTS_BETTER2;
|
|
}
|
|
if (path2->total_cost > path1->total_cost * fuzz_factor) {
|
|
/* path2 fuzzily worse on total cost */
|
|
if (path1->startup_cost > path2->startup_cost * fuzz_factor) {
|
|
/* ... but path1 fuzzily worse on startup, so DIFFERENT */
|
|
return COSTS_DIFFERENT;
|
|
}
|
|
/* else path1 dominates */
|
|
return COSTS_BETTER1;
|
|
}
|
|
/* fuzzily the same on total cost */
|
|
if (path1->startup_cost > path2->startup_cost * fuzz_factor) {
|
|
/* ... but path1 fuzzily worse on startup, so path2 wins */
|
|
return COSTS_BETTER2;
|
|
}
|
|
if (path2->startup_cost > path1->startup_cost * fuzz_factor) {
|
|
/* ... but path2 fuzzily worse on startup, so path1 wins */
|
|
return COSTS_BETTER1;
|
|
}
|
|
/* fuzzily the same on both costs */
|
|
return COSTS_EQUAL;
|
|
}
|
|
|
|
/* judge if a path is distribute key intersted path */
|
|
static bool is_itst_path(PlannerInfo* root, RelOptInfo* rel, Path* path)
|
|
{
|
|
ListCell* lc = NULL;
|
|
|
|
if (path->distribute_keys == NIL)
|
|
return false;
|
|
|
|
/* if distribute key is matching key, it's interested path */
|
|
if (equal_distributekey(root, path->distribute_keys, rel->rel_dis_keys.matching_keys))
|
|
return true;
|
|
|
|
/* if distribute key if subset of one superset key, it's interested path */
|
|
foreach (lc, rel->rel_dis_keys.superset_keys) {
|
|
List* item_list = (List*)lfirst(lc);
|
|
if (root != NULL && !needs_agg_stream(root, item_list, path->distribute_keys))
|
|
return true;
|
|
if (root == NULL && !list_difference(path->distribute_keys, item_list))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* @Description: Compare cheapest_path with path's costs.
|
|
* @in cheapest_path: Current cheapest path.
|
|
* @in path: New path.
|
|
* @return: Return cheaper path.
|
|
*/
|
|
static Path* obtain_cheaper_path(Path* cheapest_path, Path* path, CostSelector criterion)
|
|
{
|
|
int cmp;
|
|
|
|
/* We need first compare hint priority. */
|
|
if (path->hint_value > cheapest_path->hint_value) {
|
|
return path;
|
|
} else if (path->hint_value == cheapest_path->hint_value) {
|
|
/*
|
|
* If we find two paths of identical costs, try to keep the
|
|
* better-sorted one. The paths might have unrelated sort orderings,
|
|
* in which case we can only guess which might be better to keep, but
|
|
* if one is superior then we definitely should keep that one.
|
|
*/
|
|
cmp = compare_path_costs(cheapest_path, path, criterion);
|
|
if (cmp > 0 || (cmp == 0 && compare_pathkeys(cheapest_path->pathkeys, path->pathkeys) == PATHKEYS_BETTER2)) {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
return cheapest_path;
|
|
}
|
|
|
|
/*
|
|
* set_cheapest
|
|
* Find the minimum-cost paths from among a relation's paths,
|
|
* and save them in the rel's cheapest-path fields.
|
|
*
|
|
* Only unparameterized paths are considered candidates for cheapest_startup
|
|
* and cheapest_total. The cheapest_parameterized_paths list collects paths
|
|
* that are cheapest-total for their parameterization (i.e., there is no
|
|
* cheaper path with the same or weaker parameterization). This list always
|
|
* includes the unparameterized cheapest-total path, too.
|
|
*
|
|
* This is normally called only after we've finished constructing the path
|
|
* list for the rel node.
|
|
*/
|
|
void set_cheapest(RelOptInfo* parent_rel, PlannerInfo* root)
|
|
{
|
|
Path* cheapest_startup_path = NULL;
|
|
Path* cheapest_total_path = NULL;
|
|
List* cheapest_total_path_list = NIL;
|
|
bool have_parameterized_paths = false;
|
|
ListCell* p = NULL;
|
|
ListCell* l = NULL;
|
|
List* cheapest_path_list = NIL;
|
|
|
|
AssertEreport(IsA(parent_rel, RelOptInfo), MOD_OPT_JOIN, "Paramter of set_cheapest() should be RelOptInfo");
|
|
|
|
cheapest_startup_path = cheapest_total_path = NULL;
|
|
|
|
foreach (p, parent_rel->pathlist) {
|
|
Path* path = (Path*)lfirst(p);
|
|
restore_hashjoin_cost(path);
|
|
}
|
|
|
|
if (OPTIMIZE_PLAN != u_sess->attr.attr_sql.plan_mode_seed) {
|
|
/* find the random of the paths for this rel if guc plan_mode_seed is not 0 */
|
|
cheapest_path_list = get_random_path(parent_rel, &cheapest_startup_path, &cheapest_total_path);
|
|
cheapest_total_path_list = list_make1(cheapest_total_path);
|
|
} else {
|
|
List* itst_cheapest_path = NIL;
|
|
|
|
cheapest_startup_path = cheapest_total_path = NULL;
|
|
|
|
foreach (p, parent_rel->pathlist) {
|
|
Path* path = (Path*)lfirst(p);
|
|
int cmp;
|
|
|
|
/* We only consider unparameterized paths in this step */
|
|
if (path->param_info) {
|
|
have_parameterized_paths = true;
|
|
continue;
|
|
}
|
|
|
|
if (cheapest_total_path == NULL) {
|
|
cheapest_startup_path = cheapest_total_path = path;
|
|
if (is_itst_path(root, parent_rel, path))
|
|
itst_cheapest_path = lappend(itst_cheapest_path, path);
|
|
continue;
|
|
}
|
|
|
|
cheapest_startup_path = obtain_cheaper_path(cheapest_startup_path, path, STARTUP_COST);
|
|
cheapest_total_path = obtain_cheaper_path(cheapest_total_path, path, TOTAL_COST);
|
|
|
|
/* store cheapest path for different interested distribute key */
|
|
if (is_itst_path(root, parent_rel, path)) {
|
|
Path* tmp_path = NULL;
|
|
foreach (l, itst_cheapest_path) {
|
|
tmp_path = (Path*)lfirst(l);
|
|
if (equal_distributekey(root, tmp_path->distribute_keys, path->distribute_keys))
|
|
break;
|
|
}
|
|
if (l == NULL)
|
|
itst_cheapest_path = lappend(itst_cheapest_path, path);
|
|
else {
|
|
cmp = compare_path_costs(tmp_path, path, TOTAL_COST);
|
|
if (cmp > 0 ||
|
|
(cmp == 0 && compare_pathkeys(tmp_path->pathkeys, path->pathkeys) == PATHKEYS_BETTER2))
|
|
lfirst(l) = path;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* If interested path is so costed, that is, larger than cheapest path plus
|
|
* redistribute cost, we should abondon it. Else, store it in global cheapest
|
|
* path list
|
|
*/
|
|
cheapest_total_path_list = lappend(cheapest_total_path_list, cheapest_total_path);
|
|
foreach (p, itst_cheapest_path) {
|
|
Path* tmp_path = (Path*)lfirst(p);
|
|
Cost redistribute_cost = 0.0;
|
|
if (tmp_path == cheapest_total_path)
|
|
continue;
|
|
|
|
unsigned int num_datanodes = ng_get_dest_num_data_nodes(tmp_path);
|
|
|
|
compute_stream_cost(STREAM_REDISTRIBUTE,
|
|
tmp_path->locator_type,
|
|
PATH_LOCAL_ROWS(tmp_path),
|
|
tmp_path->rows,
|
|
1.0,
|
|
parent_rel->width,
|
|
false,
|
|
tmp_path->distribute_keys,
|
|
&redistribute_cost,
|
|
&tmp_path->rows,
|
|
num_datanodes,
|
|
num_datanodes);
|
|
/* only keep the path with same hint value */
|
|
if (tmp_path->total_cost < cheapest_total_path->total_cost + redistribute_cost &&
|
|
tmp_path->hint_value == cheapest_total_path->hint_value)
|
|
cheapest_total_path_list = lappend(cheapest_total_path_list, tmp_path);
|
|
if (list_length(cheapest_total_path_list) == MAX_PATH_NUM)
|
|
break;
|
|
}
|
|
list_free_ext(itst_cheapest_path);
|
|
}
|
|
|
|
if (cheapest_total_path == NULL)
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
|
errmsg("could not devise a query plan for the given query")));
|
|
|
|
parent_rel->cheapest_startup_path = cheapest_startup_path;
|
|
parent_rel->cheapest_total_path = cheapest_total_path_list;
|
|
parent_rel->cheapest_unique_path = NULL; /* computed only if needed */
|
|
/* debug info for global path */
|
|
if (log_min_messages <= DEBUG1) {
|
|
StringInfoData ds;
|
|
initStringInfo(&ds);
|
|
appendBitmapsetToString(&ds, parent_rel->relids);
|
|
ereport(DEBUG1, (errmodule(MOD_OPT_JOIN), errmsg("rel: %s", ds.data)));
|
|
pfree_ext(ds.data);
|
|
if (cheapest_startup_path != NULL)
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
errmsg("cheapest startup: %lf, %lf, hint_value: %d",
|
|
cheapest_startup_path->startup_cost,
|
|
cheapest_startup_path->total_cost,
|
|
cheapest_startup_path->hint_value)));
|
|
foreach (l, parent_rel->cheapest_total_path) {
|
|
Path* path = (Path*)lfirst(l);
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
errmsg("cheapest total: %lf, %lf, hint_value: %d",
|
|
path->startup_cost,
|
|
path->total_cost,
|
|
path->hint_value)));
|
|
elog_node_display(DEBUG1, "[OPT_JOIN] distribute key", path->distribute_keys, true);
|
|
}
|
|
}
|
|
|
|
/* Seed the parameterized-paths list with the cheapest total */
|
|
parent_rel->cheapest_parameterized_paths = list_make1(cheapest_total_path);
|
|
|
|
/* And, if there are any parameterized paths, add them in one at a time */
|
|
if (have_parameterized_paths) {
|
|
foreach (p, parent_rel->pathlist) {
|
|
Path* path = (Path*)lfirst(p);
|
|
|
|
if (path->param_info)
|
|
add_parameterized_path(parent_rel, path);
|
|
}
|
|
}
|
|
|
|
if (OPTIMIZE_PLAN != u_sess->attr.attr_sql.plan_mode_seed) {
|
|
ListCell* pnext = NULL;
|
|
for (p = list_head(parent_rel->pathlist); p != NULL; p = pnext) {
|
|
Path* path = (Path*)lfirst(p);
|
|
pnext = lnext(p);
|
|
|
|
if (!list_member_ptr(cheapest_path_list, path))
|
|
parent_rel->pathlist = list_delete_ptr(parent_rel->pathlist, path);
|
|
}
|
|
|
|
list_free_ext(cheapest_path_list);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* get_cheapest_path
|
|
* choose an optimal path from optimal path, superset key path and match path of target relation
|
|
*
|
|
* Parameters:
|
|
* @in root: planner info structure for current query level
|
|
* @in rel: final join rel with all the table referenced
|
|
* @in agg_groups: estimated local and global aggregation rows
|
|
* @in has_groupby: true if there's aggregation involved in current query level. It's used to determine if
|
|
* we should use rel's rows or aggregation rows to calculate redistribute cost
|
|
* Returns: optimal path
|
|
*/
|
|
Path* get_cheapest_path(PlannerInfo* root, RelOptInfo* rel, const double* agg_groups, bool has_groupby)
|
|
{
|
|
Path* matched_path = NULL;
|
|
Path* cheapest_path = (Path*)linitial(rel->cheapest_total_path);
|
|
Path* superset_path = NULL;
|
|
double cheapest_cost = cheapest_path->total_cost;
|
|
double gblrows;
|
|
Cost final_dis_cost = 0.0;
|
|
Cost agg_dis_cost = 0.0;
|
|
Cost path_dis_cost = 0.0;
|
|
ListCell* lc = NULL;
|
|
bool is_cheapest_super_path = false;
|
|
|
|
/* find matched path if any */
|
|
if (rel->rel_dis_keys.matching_keys != NIL) {
|
|
foreach (lc, rel->cheapest_total_path) {
|
|
Path* tmp_path = (Path*)lfirst(lc);
|
|
if (equal_distributekey(root, tmp_path->distribute_keys, rel->rel_dis_keys.matching_keys)) {
|
|
matched_path = tmp_path;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* find the cheapest path from superset key path, or mark cheapest total path as super key path */
|
|
if (is_itst_path(root, rel, cheapest_path))
|
|
is_cheapest_super_path = true;
|
|
else {
|
|
foreach (lc, rel->cheapest_total_path) {
|
|
Path* tmp_path = (Path*)lfirst(lc);
|
|
/* skip cheapest total path and matching path */
|
|
if (tmp_path == cheapest_path)
|
|
continue;
|
|
|
|
/* Get one cost least path. */
|
|
if (superset_path == NULL) {
|
|
superset_path = tmp_path;
|
|
} else if (tmp_path->hint_value > superset_path->hint_value) {
|
|
superset_path = tmp_path;
|
|
} else if (superset_path->total_cost > tmp_path->total_cost) {
|
|
superset_path = tmp_path;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* comparison between cheapest path and cheapest superset key path */
|
|
if (superset_path != NULL) {
|
|
/*
|
|
* If redistribution of aggregation is needed, we should roughly judge a minimum redistribute
|
|
* cost of redistribute+agg path or agg+redistribute+agg path
|
|
*/
|
|
if (!is_cheapest_super_path) {
|
|
unsigned int path_num_datanodes = ng_get_dest_num_data_nodes(cheapest_path);
|
|
|
|
/* redistribution cost of agg+redistribute+agg path */
|
|
compute_stream_cost(STREAM_REDISTRIBUTE,
|
|
cheapest_path->locator_type,
|
|
agg_groups[0],
|
|
cheapest_path->rows,
|
|
1.0,
|
|
rel->width,
|
|
false,
|
|
superset_path->distribute_keys,
|
|
&agg_dis_cost,
|
|
&gblrows,
|
|
path_num_datanodes,
|
|
path_num_datanodes);
|
|
/* redistribution cost of redistribute+agg path */
|
|
compute_stream_cost(STREAM_REDISTRIBUTE,
|
|
cheapest_path->locator_type,
|
|
PATH_LOCAL_ROWS(cheapest_path),
|
|
cheapest_path->rows,
|
|
1.0,
|
|
rel->width,
|
|
false,
|
|
superset_path->distribute_keys,
|
|
&path_dis_cost,
|
|
&gblrows,
|
|
path_num_datanodes,
|
|
path_num_datanodes);
|
|
cheapest_cost = cheapest_path->total_cost +
|
|
Min(agg_dis_cost * (1 + agg_groups[0] / PATH_LOCAL_ROWS(cheapest_path)), path_dis_cost);
|
|
}
|
|
/* choose super set key path if it dominates to cheapest path after redistribution */
|
|
if (cheapest_cost > superset_path->total_cost) {
|
|
cheapest_cost = superset_path->total_cost;
|
|
cheapest_path = superset_path;
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
errmsg("super path dominate: %lf, %lf", superset_path->startup_cost, superset_path->total_cost)));
|
|
elog_node_display(DEBUG1, "[OPT_JOIN] distribute key", superset_path->distribute_keys, true);
|
|
}
|
|
}
|
|
/* comparison between superset key path (cheapest path) and matching path */
|
|
if (matched_path != NULL) {
|
|
double rows;
|
|
/* determin data amount to do redistribution */
|
|
if (!has_groupby) {
|
|
rows = rel->rows;
|
|
} else {
|
|
rows = agg_groups[1];
|
|
}
|
|
/* redistribution cost to target relation */
|
|
if (cheapest_path != matched_path) {
|
|
unsigned int path_num_datanodes = ng_get_dest_num_data_nodes(cheapest_path);
|
|
|
|
compute_stream_cost(STREAM_REDISTRIBUTE,
|
|
cheapest_path->locator_type,
|
|
rows,
|
|
cheapest_path->rows,
|
|
1.0,
|
|
rel->width,
|
|
false,
|
|
matched_path->distribute_keys,
|
|
&final_dis_cost,
|
|
&gblrows,
|
|
path_num_datanodes,
|
|
path_num_datanodes);
|
|
/* choose matching key path if it dominates to cheapest path after redistribution */
|
|
if (matched_path->total_cost < cheapest_cost + final_dis_cost) {
|
|
cheapest_path = matched_path;
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
errmsg(
|
|
"matched path dominate: %lf, %lf", matched_path->startup_cost, matched_path->total_cost)));
|
|
elog_node_display(DEBUG1, "[OPT_JOIN] distribute key", matched_path->distribute_keys, true);
|
|
}
|
|
} else
|
|
ereport(DEBUG1, (errmodule(MOD_OPT_JOIN), errmsg("super path is also matched path")));
|
|
}
|
|
|
|
return cheapest_path;
|
|
}
|
|
/*
|
|
* Target :Print compare results when log_min_messages <= debug1.
|
|
* In :Compare results including cost,pathkey,BMS and rows.
|
|
* Retrun :NA
|
|
* Out :Print in the pg_log.
|
|
*/
|
|
void debug1_print_compare_result(PathCostComparison costcmp, PathKeysComparison keyscmp, BMS_Comparison outercmp,
|
|
double rowscmp, PlannerInfo* root, Path* path, bool small_fuzzy_factor_is_used)
|
|
{
|
|
StringInfoData buf;
|
|
|
|
initStringInfo(&buf);
|
|
appendStringInfoString(&buf, "\n{\n");
|
|
|
|
/* print path information */
|
|
char* path_string = debug1_print_path(root, path, 1);
|
|
appendStringInfoString(&buf, path_string);
|
|
pfree_ext(path_string);
|
|
|
|
/* print comparison results */
|
|
appendStringInfoString(&buf, "\n\tCost =");
|
|
switch (costcmp) {
|
|
case COSTS_BETTER1:
|
|
appendStringInfoString(&buf, " NewBetter\t|");
|
|
break;
|
|
case COSTS_BETTER2:
|
|
appendStringInfoString(&buf, " OldBetter\t|");
|
|
break;
|
|
case COSTS_DIFFERENT:
|
|
appendStringInfoString(&buf, " Different\t|");
|
|
break;
|
|
case COSTS_EQUAL:
|
|
appendStringInfoString(&buf, " Equal \t|");
|
|
break;
|
|
default:
|
|
appendStringInfoString(&buf, " NULL \t|");
|
|
break;
|
|
}
|
|
|
|
appendStringInfoString(&buf, "\tPathKeys =");
|
|
switch (keyscmp) {
|
|
case PATHKEYS_BETTER1:
|
|
appendStringInfoString(&buf, " NewBetter\t|");
|
|
break;
|
|
case PATHKEYS_BETTER2:
|
|
appendStringInfoString(&buf, " OldBetter\t|");
|
|
break;
|
|
case PATHKEYS_DIFFERENT:
|
|
appendStringInfoString(&buf, " Different\t|");
|
|
break;
|
|
case PATHKEYS_EQUAL:
|
|
appendStringInfoString(&buf, " Equal \t|");
|
|
break;
|
|
default:
|
|
appendStringInfoString(&buf, " NULL \t|");
|
|
break;
|
|
}
|
|
|
|
appendStringInfoString(&buf, "\t BMS =");
|
|
switch (outercmp) {
|
|
case BMS_SUBSET1:
|
|
appendStringInfoString(&buf, " NewBetter\t|");
|
|
break;
|
|
case BMS_SUBSET2:
|
|
appendStringInfoString(&buf, " OldBetter\t|");
|
|
break;
|
|
case BMS_DIFFERENT:
|
|
appendStringInfoString(&buf, " Different\t|");
|
|
break;
|
|
case BMS_EQUAL:
|
|
appendStringInfoString(&buf, " Equal \t|");
|
|
break;
|
|
default:
|
|
appendStringInfoString(&buf, " NULL \t|");
|
|
break;
|
|
}
|
|
|
|
appendStringInfoString(&buf, "\t Rows =");
|
|
if (rowscmp > 0)
|
|
appendStringInfoString(&buf, " NewLess \n");
|
|
else if (rowscmp < 0)
|
|
appendStringInfoString(&buf, " OldLess \n");
|
|
else
|
|
appendStringInfoString(&buf, " Equal \n");
|
|
|
|
if (small_fuzzy_factor_is_used)
|
|
appendStringInfoString(&buf, "\tSmall fuzzy factor is used!\n");
|
|
|
|
ereport(DEBUG1, (errmodule(MOD_OPT_JOIN), (errmsg("The old path and the comparison results are:%s}", buf.data))));
|
|
pfree_ext(buf.data);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Target :Print detail information of new path when log_min_messages <= debug1.
|
|
* In :Root path indent
|
|
* Retrun :NA
|
|
* Out :Print in the pg_log.
|
|
*/
|
|
void debug1_print_new_path(PlannerInfo* root, Path* path, bool small_fuzzy_factor_is_used)
|
|
{
|
|
StringInfoData buf;
|
|
initStringInfo(&buf);
|
|
char* path_string = debug1_print_path(root, path, 1);
|
|
appendStringInfoString(&buf, path_string);
|
|
pfree_ext(path_string);
|
|
|
|
if (small_fuzzy_factor_is_used)
|
|
appendStringInfoString(&buf, "\tSmall fuzzy factor is used!\n");
|
|
|
|
ereport(DEBUG1, (errmodule(MOD_OPT_JOIN), (errmsg("The detail information of the new path:\n{\n%s}", buf.data))));
|
|
pfree_ext(buf.data);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* @Description: Find stream hint and set hint_value.
|
|
* @in hint_state: Hint state.
|
|
* @in path: New path.
|
|
* @in inner_outer_path: Inner or outer path.
|
|
*/
|
|
static void set_stream_hint(HintState* hint_state, Path* path, Path* inner_outer_path)
|
|
{
|
|
if (hint_state == NULL) {
|
|
return;
|
|
}
|
|
|
|
Path* stream_path = inner_outer_path;
|
|
|
|
/*
|
|
* Here we need skip Material or Unique, because that can be added above stream in
|
|
* function stream_side_path.
|
|
*/
|
|
if (inner_outer_path->pathtype == T_Material) {
|
|
stream_path = ((MaterialPath*)inner_outer_path)->subpath;
|
|
} else if (inner_outer_path->pathtype == T_Unique) {
|
|
stream_path = ((UniquePath*)inner_outer_path)->subpath;
|
|
}
|
|
|
|
if (!IsA(stream_path, StreamPath)) {
|
|
return;
|
|
}
|
|
|
|
Relids rel_ids = NULL;
|
|
|
|
rel_ids = stream_path->parent->relids;
|
|
|
|
StreamPath* streamPath = (StreamPath*)stream_path;
|
|
|
|
ListCell* lc = NULL;
|
|
|
|
foreach (lc, hint_state->stream_hint) {
|
|
StreamHint* stream_hint = (StreamHint*)lfirst(lc);
|
|
|
|
if (bms_equal(stream_hint->joinrelids, rel_ids)) {
|
|
if (stream_hint->stream_type == streamPath->type) {
|
|
stream_hint->base.state = HINT_STATE_USED;
|
|
if (stream_hint->negative)
|
|
path->hint_value--;
|
|
else
|
|
path->hint_value++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @Description: Find scan hint and set hint_value.
|
|
* @in new_path: New path.
|
|
* @in hstate: Hint state.
|
|
*/
|
|
static void set_scan_hint(Path* new_path, HintState* hstate)
|
|
{
|
|
ScanMethodHint* scanHint = NULL;
|
|
|
|
switch (new_path->pathtype) {
|
|
case T_SeqScan:
|
|
case T_CStoreScan:
|
|
case T_DfsScan:
|
|
case T_SubqueryScan:
|
|
case T_ForeignScan: {
|
|
scanHint = find_scan_hint(hstate, new_path->parent->relids, HINT_KEYWORD_TABLESCAN);
|
|
break;
|
|
}
|
|
case T_IndexScan: {
|
|
scanHint = find_scan_hint(hstate, new_path->parent->relids, HINT_KEYWORD_INDEXSCAN);
|
|
break;
|
|
}
|
|
case T_IndexOnlyScan: {
|
|
scanHint = find_scan_hint(hstate, new_path->parent->relids, HINT_KEYWORD_INDEXONLYSCAN);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (scanHint != NULL && scanHint->indexlist != NIL) {
|
|
IndexPath* index_path = (IndexPath*)new_path;
|
|
char* index_name = get_rel_name(index_path->indexinfo->indexoid);
|
|
char* hint_index_name = strVal(linitial(scanHint->indexlist));
|
|
|
|
if (index_name && strncmp(hint_index_name, index_name, strlen(index_name) + 1) != 0) {
|
|
scanHint = NULL;
|
|
}
|
|
}
|
|
|
|
if (scanHint != NULL) {
|
|
scanHint->base.state = HINT_STATE_USED;
|
|
if (scanHint->negative)
|
|
new_path->hint_value--;
|
|
else
|
|
new_path->hint_value++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @Description: Set path's hint kewword.
|
|
* @in join_rel: Join relation information.
|
|
* @in new_path: Generate new path.
|
|
* @in hstate: Current query hint state.
|
|
*/
|
|
static void set_join_hint(RelOptInfo* join_rel, JoinPath* new_join_path, HintState* hstate)
|
|
{
|
|
List* hints = NIL;
|
|
Relids joinrelids = join_rel->relids;
|
|
Relids inner_relids = new_join_path->innerjoinpath->parent->relids;
|
|
|
|
switch (new_join_path->path.pathtype) {
|
|
case T_NestLoop:
|
|
hints = find_specific_join_hint(hstate, joinrelids, inner_relids, HINT_KEYWORD_NESTLOOP);
|
|
break;
|
|
case T_MergeJoin:
|
|
hints = find_specific_join_hint(hstate, joinrelids, inner_relids, HINT_KEYWORD_MERGEJOIN);
|
|
break;
|
|
case T_HashJoin:
|
|
hints = find_specific_join_hint(hstate, joinrelids, inner_relids, HINT_KEYWORD_HASHJOIN);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ListCell* lc = NULL;
|
|
foreach (lc, hints) {
|
|
JoinMethodHint* hint = (JoinMethodHint*)lfirst(lc);
|
|
hint->base.state = HINT_STATE_USED;
|
|
if (hint->negative) {
|
|
new_join_path->path.hint_value--;
|
|
} else {
|
|
new_join_path->path.hint_value++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @Description: Skip not join path and find hinted path.
|
|
* @in current_path: Curent path.
|
|
* @return: Join path or scan path.
|
|
*/
|
|
Path* find_hinted_path(Path* current_path)
|
|
{
|
|
Path* path = current_path;
|
|
|
|
while (path != NULL) {
|
|
if (path->pathtype == T_Material) {
|
|
path = ((MaterialPath*)path)->subpath;
|
|
} else if (path->pathtype == T_Stream) {
|
|
path = ((StreamPath*)path)->subpath;
|
|
} else if (path->pathtype == T_Unique) {
|
|
path = ((UniquePath*)path)->subpath;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
/*
|
|
* @Description: Inherit child path's hint value.
|
|
* @in new_path: New join path.
|
|
* @in outer_path: Outer path.
|
|
* @in inner_path: Inner path.
|
|
*/
|
|
static void inherit_child_hintvalue(Path* new_path, Path* outer_path, Path* inner_path)
|
|
{
|
|
/* We keep hint value only in join path. */
|
|
Path* outer_join_path = find_hinted_path(outer_path);
|
|
Path* inner_join_path = find_hinted_path(inner_path);
|
|
|
|
new_path->hint_value += outer_join_path->hint_value + inner_join_path->hint_value;
|
|
}
|
|
|
|
/*
|
|
* @Description: Set hint values to this new path.
|
|
* @in join_rel: Join relition.
|
|
* @in new_path: New path.
|
|
* @in hstate: Hint state.
|
|
*/
|
|
void set_hint_value(RelOptInfo* join_rel, Path* new_path, HintState* hstate)
|
|
{
|
|
if (hstate == NULL) {
|
|
return;
|
|
}
|
|
|
|
AssertEreport(new_path->hint_value == 0, MOD_OPT, "");
|
|
|
|
set_scan_hint(new_path, hstate);
|
|
|
|
/* Deal with join path. */
|
|
if (IsA(new_path, NestPath) || IsA(new_path, MergePath) || IsA(new_path, HashPath)) {
|
|
JoinPath* join_path = (JoinPath*)new_path;
|
|
|
|
Path* outer_path = join_path->outerjoinpath;
|
|
Path* inner_path = join_path->innerjoinpath;
|
|
|
|
set_join_hint(join_rel, (JoinPath*)new_path, hstate);
|
|
|
|
set_stream_hint(hstate, new_path, outer_path);
|
|
|
|
set_stream_hint(hstate, new_path, inner_path);
|
|
|
|
inherit_child_hintvalue(new_path, outer_path, inner_path);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* add_path
|
|
* Consider a potential implementation path for the specified parent rel,
|
|
* and add it to the rel's pathlist if it is worthy of consideration.
|
|
* A path is worthy if it has a better sort order (better pathkeys) or
|
|
* cheaper cost (on either dimension), or generates fewer rows, than any
|
|
* existing path that has the same or superset parameterization rels.
|
|
*
|
|
* We also remove from the rel's pathlist any old paths that are dominated
|
|
* by new_path --- that is, new_path is cheaper, at least as well ordered,
|
|
* generates no more rows, and requires no outer rels not required by the
|
|
* old path.
|
|
*
|
|
* In most cases, a path with a superset parameterization will generate
|
|
* fewer rows (since it has more join clauses to apply), so that those two
|
|
* figures of merit move in opposite directions; this means that a path of
|
|
* one parameterization can seldom dominate a path of another. But such
|
|
* cases do arise, so we make the full set of checks anyway.
|
|
*
|
|
* There is one policy decision embedded in this function, along with its
|
|
* sibling add_path_precheck: we treat all parameterized paths as having
|
|
* NIL pathkeys, so that they compete only on cost. This is to reduce
|
|
* the number of parameterized paths that are kept. See discussion in
|
|
* src/backend/optimizer/README.
|
|
*
|
|
* The pathlist is kept sorted by total_cost, with cheaper paths
|
|
* at the front. Within this routine, that's simply a speed hack:
|
|
* doing it that way makes it more likely that we will reject an inferior
|
|
* path after a few comparisons, rather than many comparisons.
|
|
* However, add_path_precheck relies on this ordering to exit early
|
|
* when possible.
|
|
*
|
|
* NOTE: discarded Path objects are immediately pfree'd to reduce planner
|
|
* memory consumption. We dare not try to free the substructure of a Path,
|
|
* since much of it may be shared with other Paths or the query tree itself;
|
|
* but just recycling discarded Path nodes is a very useful savings in
|
|
* a large join tree. We can recycle the List nodes of pathlist, too.
|
|
*
|
|
* BUT: we do not pfree IndexPath objects, since they may be referenced as
|
|
* children of BitmapHeapPaths as well as being paths in their own right.
|
|
*
|
|
* 'parent_rel' is the relation entry to which the path corresponds.
|
|
* 'new_path' is a potential path for parent_rel.
|
|
*
|
|
* Returns nothing, but modifies parent_rel->pathlist.
|
|
*/
|
|
void add_path(PlannerInfo* root, RelOptInfo* parent_rel, Path* new_path)
|
|
{
|
|
bool accept_new = true; /* unless we find a superior old path */
|
|
ListCell* insert_after = NULL; /* where to insert new item */
|
|
List* new_path_pathkeys = NIL;
|
|
ListCell* p1 = NULL;
|
|
ListCell* p1_prev = NULL;
|
|
ListCell* p1_next = NULL;
|
|
bool small_fuzzy_factor_is_used = false;
|
|
|
|
/*
|
|
* This is a convenient place to check for query cancel --- no part of the
|
|
* planner goes very long without calling add_path().
|
|
*/
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
/*
|
|
* In Stream mode, it's not supported if there's param push under stream.
|
|
* So we skip this path in advance to avoid other paths are generated.
|
|
*/
|
|
if (IS_STREAM_PLAN && IsA(new_path, NestPath)) {
|
|
NestPath* np = (NestPath*)new_path;
|
|
bool invalid = false;
|
|
ContainStreamContext context;
|
|
|
|
context.outer_relids = np->outerjoinpath->parent->relids;
|
|
context.only_check_stream = false;
|
|
context.under_materialize_all = false;
|
|
context.has_stream = false;
|
|
context.has_parameterized_path = false;
|
|
context.has_cstore_index_delta = false;
|
|
|
|
stream_path_walker(np->innerjoinpath, &context);
|
|
|
|
/*
|
|
* In Executor engine, we'll materializeAll to prevent deadlock when
|
|
* either outer or inner has stream, and meanwhile if there's parameterized
|
|
* path, it's forbidden, so we should exclude it to the candidate
|
|
*/
|
|
if (context.has_parameterized_path) {
|
|
/* inner has stream */
|
|
if (context.has_stream || context.has_cstore_index_delta)
|
|
invalid = true;
|
|
/* If inner is not material, materializeAll is not used, so skip outer check */
|
|
else if (IsA(np->innerjoinpath, MaterialPath)) {
|
|
context.outer_relids = NULL;
|
|
context.only_check_stream = false;
|
|
context.under_materialize_all = false;
|
|
context.has_stream = false;
|
|
context.has_parameterized_path = false;
|
|
context.has_cstore_index_delta = false;
|
|
|
|
stream_path_walker(np->outerjoinpath, &context);
|
|
/* outer has stream */
|
|
if (context.has_stream || context.has_cstore_index_delta)
|
|
invalid = true;
|
|
}
|
|
}
|
|
|
|
if (invalid) {
|
|
pfree_ext(new_path);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Set path's hint_value. */
|
|
if (root != NULL && root->parse->hintState != NULL) {
|
|
set_hint_value(parent_rel, new_path, root->parse->hintState);
|
|
}
|
|
|
|
if (OPTIMIZE_PLAN != u_sess->attr.attr_sql.plan_mode_seed) {
|
|
parent_rel->pathlist = lcons(new_path, parent_rel->pathlist);
|
|
return;
|
|
}
|
|
|
|
/* Pretend parameterized paths have no pathkeys, per comment above */
|
|
new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;
|
|
|
|
/*
|
|
* Loop to check proposed new path against old paths. Note it is possible
|
|
* for more than one old path to be tossed out because new_path dominates
|
|
* it.
|
|
*
|
|
* We can't use foreach here because the loop body may delete the current
|
|
* list cell.
|
|
*/
|
|
p1_prev = NULL;
|
|
for (p1 = list_head(parent_rel->pathlist); p1 != NULL; p1 = p1_next) {
|
|
Path* old_path = (Path*)lfirst(p1);
|
|
bool remove_old = false; /* unless new proves superior */
|
|
bool eq_diskey = true;
|
|
PathCostComparison costcmp = COSTS_DIFFERENT;
|
|
PathKeysComparison keyscmp = PATHKEYS_DIFFERENT;
|
|
BMS_Comparison outercmp = BMS_DIFFERENT;
|
|
double rowscmp;
|
|
|
|
p1_next = lnext(p1);
|
|
|
|
/*
|
|
* Do a fuzzy cost comparison with 1% fuzziness limit. (XXX does this
|
|
* percentage need to be user-configurable?)
|
|
*/
|
|
costcmp = compare_path_costs_fuzzily(new_path, old_path, FUZZY_FACTOR);
|
|
|
|
/*
|
|
* If the two paths compare differently for startup and total cost,
|
|
* then we want to keep both, and we can skip comparing pathkeys and
|
|
* required_outer rels. If they compare the same, proceed with the
|
|
* other comparisons. Row count is checked last. (We make the tests
|
|
* in this order because the cost comparison is most likely to turn
|
|
* out "different", and the pathkeys comparison next most likely. As
|
|
* explained above, row count very seldom makes a difference, so even
|
|
* though it's cheap to compare there's not much point in checking it
|
|
* earlier.)
|
|
*/
|
|
if (costcmp != COSTS_DIFFERENT) {
|
|
/* Similarly check to see if either dominates on pathkeys */
|
|
List* old_path_pathkeys = NIL;
|
|
|
|
old_path_pathkeys = old_path->param_info ? NIL : old_path->pathkeys;
|
|
keyscmp = compare_pathkeys(new_path_pathkeys, old_path_pathkeys);
|
|
if (keyscmp != PATHKEYS_DIFFERENT) {
|
|
switch (costcmp) {
|
|
case COSTS_EQUAL:
|
|
outercmp = bms_subset_compare(PATH_REQ_OUTER(new_path), PATH_REQ_OUTER(old_path));
|
|
if (keyscmp == PATHKEYS_BETTER1) {
|
|
if ((outercmp == BMS_EQUAL || outercmp == BMS_SUBSET1) && new_path->rows <= old_path->rows)
|
|
remove_old = true; /* new dominates old */
|
|
} else if (keyscmp == PATHKEYS_BETTER2) {
|
|
if ((outercmp == BMS_EQUAL || outercmp == BMS_SUBSET2) && new_path->rows >= old_path->rows)
|
|
accept_new = false; /* old dominates new */
|
|
} else {
|
|
if (outercmp == BMS_EQUAL) {
|
|
/*
|
|
* Same pathkeys and outer rels, and fuzzily
|
|
* the same cost, so keep just one; to decide
|
|
* which, first check rows and then do a fuzzy
|
|
* cost comparison with very small fuzz limit.
|
|
* (We used to do an exact cost comparison,
|
|
* but that results in annoying
|
|
* platform-specific plan variations due to
|
|
* roundoff in the cost estimates.) If things
|
|
* are still tied, arbitrarily keep only the
|
|
* old path. Notice that we will keep only
|
|
* the old path even if the less-fuzzy
|
|
* comparison decides the startup and total
|
|
* costs compare differently.
|
|
*/
|
|
if (new_path->rows < old_path->rows)
|
|
remove_old = true; /* new dominates old */
|
|
else if (new_path->rows > old_path->rows)
|
|
accept_new = false; /* old dominates new */
|
|
else {
|
|
small_fuzzy_factor_is_used = true;
|
|
if (compare_path_costs_fuzzily(new_path, old_path, SMALL_FUZZY_FACTOR) ==
|
|
COSTS_BETTER1)
|
|
remove_old = true; /* new dominates old */
|
|
else
|
|
accept_new = false; /* old equals or dominates new */
|
|
}
|
|
} else if (outercmp == BMS_SUBSET1 && new_path->rows <= old_path->rows)
|
|
remove_old = true; /* new dominates old */
|
|
else if (outercmp == BMS_SUBSET2 && new_path->rows >= old_path->rows)
|
|
accept_new = false; /* old dominates new */
|
|
/* else different parameterizations, keep both */
|
|
}
|
|
break;
|
|
case COSTS_BETTER1:
|
|
if (keyscmp != PATHKEYS_BETTER2) {
|
|
outercmp = bms_subset_compare(PATH_REQ_OUTER(new_path), PATH_REQ_OUTER(old_path));
|
|
if ((outercmp == BMS_EQUAL || outercmp == BMS_SUBSET1) && new_path->rows <= old_path->rows)
|
|
remove_old = true; /* new dominates old */
|
|
}
|
|
break;
|
|
case COSTS_BETTER2:
|
|
if (keyscmp != PATHKEYS_BETTER1) {
|
|
outercmp = bms_subset_compare(PATH_REQ_OUTER(new_path), PATH_REQ_OUTER(old_path));
|
|
if ((outercmp == BMS_EQUAL || outercmp == BMS_SUBSET2) && new_path->rows >= old_path->rows)
|
|
accept_new = false; /* old dominates new */
|
|
}
|
|
break;
|
|
default:
|
|
|
|
/*
|
|
* can't get here, but keep this case to keep compiler
|
|
* quiet
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN)
|
|
eq_diskey = equal_distributekey(root, new_path->distribute_keys, old_path->distribute_keys);
|
|
#endif
|
|
/*
|
|
* Remove current element from pathlist if dominated by new.
|
|
*/
|
|
#ifdef STREAMPLAN
|
|
if (remove_old && eq_diskey) {
|
|
#else
|
|
if (remove_old) {
|
|
#endif
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
(errmsg("An old path is removed with cost = %lf .. %lf; rows = %lf",
|
|
old_path->startup_cost,
|
|
old_path->total_cost,
|
|
old_path->rows))));
|
|
rowscmp = old_path->rows - new_path->rows;
|
|
if (log_min_messages <= DEBUG1)
|
|
debug1_print_compare_result(
|
|
costcmp, keyscmp, outercmp, rowscmp, root, old_path, small_fuzzy_factor_is_used);
|
|
parent_rel->pathlist = list_delete_cell(parent_rel->pathlist, p1, p1_prev);
|
|
|
|
/*
|
|
* Delete the data pointed-to by the deleted cell, if possible
|
|
*/
|
|
if (!IsA(old_path, IndexPath))
|
|
pfree_ext(old_path);
|
|
/* p1_prev does not advance */
|
|
} else {
|
|
/* new belongs after this old path if it has cost >= old's */
|
|
if (new_path->total_cost >= old_path->total_cost && new_path->hint_value <= old_path->hint_value)
|
|
insert_after = p1;
|
|
/* p1_prev advances */
|
|
p1_prev = p1;
|
|
}
|
|
|
|
#ifdef STREAMPLAN
|
|
/* we should accept the new if distribute key differs */
|
|
if (!accept_new && !eq_diskey) {
|
|
accept_new = true;
|
|
/* new belongs after this old path if it has cost >= old's */
|
|
if (new_path->total_cost >= old_path->total_cost && new_path->hint_value <= old_path->hint_value)
|
|
insert_after = p1;
|
|
/* p1_prev advances */
|
|
p1_prev = p1;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* If we found an old path that dominates new_path, we can quit
|
|
* scanning the pathlist; we will not add new_path, and we assume
|
|
* new_path cannot dominate any other elements of the pathlist.
|
|
*/
|
|
if (!accept_new) {
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
(errmsg("A new path is not accepted with cost = %lf .. %lf; rows = %lf",
|
|
new_path->startup_cost,
|
|
new_path->total_cost,
|
|
new_path->rows))));
|
|
rowscmp = old_path->rows - new_path->rows;
|
|
if (log_min_messages <= DEBUG1) {
|
|
debug1_print_new_path(root, new_path, small_fuzzy_factor_is_used);
|
|
debug1_print_compare_result(
|
|
costcmp, keyscmp, outercmp, rowscmp, root, old_path, small_fuzzy_factor_is_used);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (accept_new) {
|
|
/* Accept the new path: insert it at proper place in pathlist */
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
(errmsg("A new path is accepted with cost = %lf .. %lf; rows = %lf",
|
|
new_path->startup_cost,
|
|
new_path->total_cost,
|
|
new_path->rows))));
|
|
if (log_min_messages <= DEBUG1)
|
|
debug1_print_new_path(root, new_path, small_fuzzy_factor_is_used);
|
|
if (insert_after != NULL)
|
|
lappend_cell(parent_rel->pathlist, insert_after, new_path);
|
|
else
|
|
parent_rel->pathlist = lcons(new_path, parent_rel->pathlist);
|
|
} else {
|
|
/* Reject and recycle the new path */
|
|
if (!IsA(new_path, IndexPath))
|
|
pfree_ext(new_path);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* add_path_precheck
|
|
* Check whether a proposed new path could possibly get accepted.
|
|
* We assume we know the path's pathkeys and parameterization accurately,
|
|
* and have lower bounds for its costs.
|
|
*
|
|
* Note that we do not know the path's rowcount, since getting an estimate for
|
|
* that is too expensive to do before prechecking. We assume here that paths
|
|
* of a superset parameterization will generate fewer rows; if that holds,
|
|
* then paths with different parameterizations cannot dominate each other
|
|
* and so we can simply ignore existing paths of another parameterization.
|
|
* (In the infrequent cases where that rule of thumb fails, add_path will
|
|
* get rid of the inferior path.)
|
|
*
|
|
* At the time this is called, we haven't actually built a Path structure,
|
|
* so the required information has to be passed piecemeal.
|
|
*/
|
|
bool add_path_precheck(
|
|
RelOptInfo* parent_rel, Cost startup_cost, Cost total_cost, List* pathkeys, Relids required_outer)
|
|
{
|
|
List* new_path_pathkeys = NIL;
|
|
ListCell* p1 = NULL;
|
|
|
|
/* Pretend parameterized paths have no pathkeys, per add_path comment */
|
|
new_path_pathkeys = required_outer ? NIL : pathkeys;
|
|
|
|
foreach (p1, parent_rel->pathlist) {
|
|
Path* old_path = (Path*)lfirst(p1);
|
|
PathKeysComparison keyscmp;
|
|
double fuzzy_factor = IS_STREAM_PLAN ? FUZZY_FACTOR : 1.0;
|
|
|
|
/*
|
|
* We are looking for an old_path with the same parameterization (and
|
|
* by assumption the same rowcount) that dominates the new path on
|
|
* pathkeys as well as both cost metrics. If we find one, we can
|
|
* reject the new path.
|
|
*
|
|
* For speed, we make exact rather than fuzzy cost comparisons. If an
|
|
* old path dominates the new path exactly on both costs, it will
|
|
* surely do so fuzzily. However, in stream case, this is just a initial
|
|
* rough estimation, so use fuzzy cost instead.
|
|
*/
|
|
if (total_cost >= old_path->total_cost * fuzzy_factor) {
|
|
if (startup_cost >= old_path->startup_cost) {
|
|
List* old_path_pathkeys = NIL;
|
|
|
|
old_path_pathkeys = old_path->param_info ? NIL : old_path->pathkeys;
|
|
keyscmp = compare_pathkeys(new_path_pathkeys, old_path_pathkeys);
|
|
if (keyscmp == PATHKEYS_EQUAL || keyscmp == PATHKEYS_BETTER2) {
|
|
if (bms_equal(required_outer, PATH_REQ_OUTER(old_path))) {
|
|
/* Found an old path that dominates the new one */
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
(errmsg("--Precheck drop new path: startup_cost = %lf; total_cost = %lf",
|
|
startup_cost,
|
|
total_cost))));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* Since the pathlist is sorted by total_cost, we can stop looking
|
|
* once we reach a path with a total_cost larger than the new
|
|
* path's.
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* add_parameterized_path
|
|
* Consider a parameterized implementation path for the specified rel,
|
|
* and add it to the rel's cheapest_parameterized_paths list if it
|
|
* belongs there, removing any old entries that it dominates.
|
|
*
|
|
* This is essentially a cut-down form of add_path(): we do not care
|
|
* about startup cost or sort ordering, only total cost, rowcount, and
|
|
* parameterization. Also, we must not recycle rejected paths, since
|
|
* they will still be present in the rel's pathlist.
|
|
*
|
|
* 'parent_rel' is the relation entry to which the path corresponds.
|
|
* 'new_path' is a parameterized path for parent_rel.
|
|
*
|
|
* Returns nothing, but modifies parent_rel->cheapest_parameterized_paths.
|
|
*/
|
|
static void add_parameterized_path(RelOptInfo* parent_rel, Path* new_path)
|
|
{
|
|
bool accept_new = true; /* unless we find a superior old path */
|
|
ListCell* insert_after = NULL; /* where to insert new item */
|
|
ListCell* p1 = NULL;
|
|
ListCell* p1_prev = NULL;
|
|
ListCell* p1_next = NULL;
|
|
|
|
/*
|
|
* Loop to check proposed new path against old paths. Note it is possible
|
|
* for more than one old path to be tossed out because new_path dominates
|
|
* it.
|
|
*
|
|
* We can't use foreach here because the loop body may delete the current
|
|
* list cell.
|
|
*/
|
|
for (p1 = list_head(parent_rel->cheapest_parameterized_paths); p1 != NULL; p1 = p1_next) {
|
|
Path* old_path = (Path*)lfirst(p1);
|
|
bool remove_old = false; /* unless new proves superior */
|
|
int costcmp;
|
|
BMS_Comparison outercmp;
|
|
|
|
p1_next = lnext(p1);
|
|
|
|
costcmp = compare_path_costs(new_path, old_path, TOTAL_COST);
|
|
outercmp = bms_subset_compare(PATH_REQ_OUTER(new_path), PATH_REQ_OUTER(old_path));
|
|
if (outercmp != BMS_DIFFERENT) {
|
|
if (costcmp < 0) {
|
|
if (outercmp != BMS_SUBSET2 && new_path->rows <= old_path->rows)
|
|
remove_old = true; /* new dominates old */
|
|
} else if (costcmp > 0) {
|
|
if (outercmp != BMS_SUBSET1 && new_path->rows >= old_path->rows)
|
|
accept_new = false; /* old dominates new */
|
|
} else if (outercmp == BMS_SUBSET1 && new_path->rows <= old_path->rows)
|
|
remove_old = true; /* new dominates old */
|
|
else if (outercmp == BMS_SUBSET2 && new_path->rows >= old_path->rows)
|
|
accept_new = false; /* old dominates new */
|
|
else if (new_path->rows < old_path->rows)
|
|
remove_old = true; /* new dominates old */
|
|
else {
|
|
/* Same cost, rows, and param rels; arbitrarily keep old */
|
|
accept_new = false; /* old equals or dominates new */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Remove current element from cheapest_parameterized_paths if
|
|
* dominated by new.
|
|
*/
|
|
if (remove_old) {
|
|
parent_rel->cheapest_parameterized_paths =
|
|
list_delete_cell(parent_rel->cheapest_parameterized_paths, p1, p1_prev);
|
|
/* p1_prev does not advance */
|
|
} else {
|
|
/* new belongs after this old path if it has cost >= old's */
|
|
if (costcmp >= 0)
|
|
insert_after = p1;
|
|
/* p1_prev advances */
|
|
p1_prev = p1;
|
|
}
|
|
|
|
/*
|
|
* If we found an old path that dominates new_path, we can quit
|
|
* scanning the list; we will not add new_path, and we assume new_path
|
|
* cannot dominate any other elements of the list.
|
|
*/
|
|
if (!accept_new)
|
|
break;
|
|
}
|
|
|
|
if (accept_new) {
|
|
/* Accept the new path: insert it at proper place in list */
|
|
if (insert_after != NULL)
|
|
lappend_cell(parent_rel->cheapest_parameterized_paths, insert_after, new_path);
|
|
else
|
|
parent_rel->cheapest_parameterized_paths = lcons(new_path, parent_rel->cheapest_parameterized_paths);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* PATH NODE CREATION ROUTINES
|
|
*****************************************************************************/
|
|
/*
|
|
* create_seqscan_path
|
|
* Creates a path corresponding to a sequential scan, returning the
|
|
* pathnode.
|
|
*/
|
|
Path* create_seqscan_path(PlannerInfo* root, RelOptInfo* rel, Relids required_outer, int dop)
|
|
{
|
|
Path* pathnode = makeNode(Path);
|
|
|
|
pathnode->pathtype = T_SeqScan;
|
|
pathnode->parent = rel;
|
|
pathnode->param_info = get_baserel_parampathinfo(root, rel, required_outer);
|
|
pathnode->pathkeys = NIL; /* seqscan has unordered result */
|
|
pathnode->dop = dop;
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
pathnode->distribute_keys = rel->distribute_keys;
|
|
pathnode->locator_type = rel->locator_type;
|
|
|
|
/* add location information for seqscan path */
|
|
RangeTblEntry* rte = root->simple_rte_array[rel->relid];
|
|
Distribution* distribution = ng_get_baserel_data_distribution(rte->relid, rte->relkind);
|
|
ng_copy_distribution(&pathnode->distribution, distribution);
|
|
if (InvalidOid == pathnode->distribution.group_oid || bms_is_empty(pathnode->distribution.bms_data_nodeids)) {
|
|
elog(DEBUG1, "[create_seqscan_path] bms is empty. tableoid [%u] relkind [%c]", rte->relid, rte->relkind);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
RangeTblEntry* rte = planner_rt_fetch(rel->relid, root);
|
|
if (NULL == rte->tablesample) {
|
|
cost_seqscan(pathnode, root, rel, pathnode->param_info);
|
|
} else {
|
|
AssertEreport(rte->rtekind == RTE_RELATION, MOD_OPT_JOIN, "Rel should be base relation");
|
|
cost_samplescan(pathnode, root, rel, pathnode->param_info);
|
|
}
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
Path* build_seqScanPath_by_indexScanPath(PlannerInfo* root, Path* index_path)
|
|
{
|
|
Path* pathnode = makeNode(Path);
|
|
|
|
pathnode->pathtype = T_SeqScan;
|
|
pathnode->parent = index_path->parent;
|
|
pathnode->param_info = index_path->param_info;
|
|
pathnode->pathkeys = NIL;
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
pathnode->distribute_keys = index_path->distribute_keys;
|
|
pathnode->locator_type = index_path->locator_type;
|
|
|
|
/* add location information for seqscan path by index scan path */
|
|
RelOptInfo* rel = pathnode->parent;
|
|
RangeTblEntry* rte = root->simple_rte_array[rel->relid];
|
|
Distribution* distribution = ng_get_baserel_data_distribution(rte->relid, rte->relkind);
|
|
ng_copy_distribution(&pathnode->distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
cost_seqscan(pathnode, root, pathnode->parent, pathnode->param_info);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_cstorescan_path with dop parm for parallelism
|
|
* Creates a path corresponding to a column store scan, returning the
|
|
* pathnode.
|
|
*/
|
|
Path* create_cstorescan_path(PlannerInfo* root, RelOptInfo* rel, int dop)
|
|
{
|
|
Path* pathnode = makeNode(Path);
|
|
|
|
pathnode->parent = rel;
|
|
pathnode->pathkeys = NIL; /* seqscan has unordered result */
|
|
pathnode->dop = dop;
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
pathnode->distribute_keys = rel->distribute_keys;
|
|
pathnode->locator_type = rel->locator_type;
|
|
|
|
/* add location information for cstorescan path */
|
|
RangeTblEntry* rte = root->simple_rte_array[rel->relid];
|
|
Distribution* distribution = ng_get_baserel_data_distribution(rte->relid, rte->relkind);
|
|
ng_copy_distribution(&pathnode->distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
pathnode->pathtype = (REL_COL_ORIENTED == rel->orientation) ? T_CStoreScan : T_DfsScan;
|
|
|
|
RangeTblEntry* rte = planner_rt_fetch(rel->relid, root);
|
|
if (NULL == rte->tablesample) {
|
|
if (REL_COL_ORIENTED == rel->orientation) {
|
|
cost_cstorescan(pathnode, root, rel);
|
|
} else {
|
|
/* PAX on hdfs. */
|
|
AssertEreport(REL_PAX_ORIENTED == rel->orientation, MOD_OPT_JOIN, "Rel should be PAX on hdfs");
|
|
cost_dfsscan(pathnode, root, rel);
|
|
}
|
|
} else {
|
|
AssertEreport(rte->rtekind == RTE_RELATION, MOD_OPT_JOIN, "Rel should be base relation");
|
|
cost_samplescan(pathnode, root, rel, pathnode->param_info);
|
|
}
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_tstorescan_path with dop parm for parallelism
|
|
* Creates a path corresponding to a time series store scan, returning the
|
|
* pathnode.
|
|
*/
|
|
Path* create_tsstorescan_path(PlannerInfo *root, RelOptInfo *rel, int dop)
|
|
{
|
|
Path *pathnode = makeNode(Path);
|
|
|
|
pathnode->pathtype = T_TsStoreScan;
|
|
pathnode->parent = rel;
|
|
pathnode->pathkeys = NIL; /* seqscan has unordered result */
|
|
pathnode->dop = dop;
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN)
|
|
{
|
|
pathnode->distribute_keys = rel->distribute_keys;
|
|
pathnode->locator_type = rel->locator_type;
|
|
|
|
/* add location information for tsstorescan path */
|
|
RangeTblEntry *rte = root->simple_rte_array[rel->relid];
|
|
Distribution *distribution = ng_get_baserel_data_distribution(rte->relid, rte->relkind);
|
|
ng_copy_distribution(&pathnode->distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
RangeTblEntry *rte = planner_rt_fetch(rel->relid, root);
|
|
if (NULL == rte->tablesample)
|
|
{
|
|
cost_tsstorescan(pathnode, root, rel);
|
|
}
|
|
else
|
|
{
|
|
AssertEreport(rte->rtekind == RTE_RELATION,
|
|
MOD_OPT_JOIN, "Rel should be base relation");
|
|
cost_samplescan(pathnode, root, rel, pathnode->param_info);
|
|
}
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* Check whether the bitmap heap path just use global partition index.
|
|
*/
|
|
bool CheckBitmapQualIsGlobalIndex(Path* bitmapqual)
|
|
{
|
|
bool bitmapqualIsGlobal = true;
|
|
if (IsA(bitmapqual, IndexPath)) {
|
|
IndexPath* ipath = (IndexPath*)bitmapqual;
|
|
bitmapqualIsGlobal = ipath->indexinfo->isGlobal;
|
|
} else if (IsA(bitmapqual, BitmapAndPath)) {
|
|
BitmapAndPath* apath = (BitmapAndPath*)bitmapqual;
|
|
ListCell* l = NULL;
|
|
bool allIsGlobal = true;
|
|
|
|
foreach (l, apath->bitmapquals) {
|
|
if (CheckBitmapQualIsGlobalIndex((Path*)lfirst(l)) != allIsGlobal) {
|
|
bitmapqualIsGlobal = !allIsGlobal;
|
|
break;
|
|
}
|
|
}
|
|
} else if (IsA(bitmapqual, BitmapOrPath)) {
|
|
BitmapOrPath* opath = (BitmapOrPath*)bitmapqual;
|
|
ListCell* l = NULL;
|
|
bool allIsGlobal = true;
|
|
|
|
foreach (l, opath->bitmapquals) {
|
|
if (CheckBitmapQualIsGlobalIndex((Path*)lfirst(l)) != allIsGlobal) {
|
|
bitmapqualIsGlobal = !allIsGlobal;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
|
|
errmsg("unrecognized node type: %d", nodeTag(bitmapqual))));
|
|
}
|
|
|
|
return bitmapqualIsGlobal;
|
|
}
|
|
|
|
/*
|
|
* Check whether have global partition index or local partition index in bitmap heap path,
|
|
* Contains at least one, return true.
|
|
*/
|
|
bool CheckBitmapHeapPathContainGlobalOrLocal(Path* bitmapqual)
|
|
{
|
|
bool containGlobalOrLocal = false;
|
|
if (IsA(bitmapqual, BitmapAndPath)) {
|
|
BitmapAndPath* apath = (BitmapAndPath*)bitmapqual;
|
|
ListCell* l = NULL;
|
|
|
|
foreach (l, apath->bitmapquals) {
|
|
containGlobalOrLocal = CheckBitmapHeapPathContainGlobalOrLocal((Path*)lfirst(l));
|
|
if (containGlobalOrLocal)
|
|
break;
|
|
}
|
|
} else if (IsA(bitmapqual, BitmapOrPath)) {
|
|
BitmapOrPath* opath = (BitmapOrPath*)bitmapqual;
|
|
ListCell* head = list_head(opath->bitmapquals);
|
|
ListCell* l = NULL;
|
|
bool allIsGlobal = CheckBitmapQualIsGlobalIndex((Path*)lfirst(head));
|
|
|
|
foreach (l, opath->bitmapquals) {
|
|
if (l == head) {
|
|
continue;
|
|
}
|
|
if (CheckBitmapQualIsGlobalIndex((Path*)lfirst(l)) != allIsGlobal) {
|
|
containGlobalOrLocal = true;
|
|
break;
|
|
}
|
|
}
|
|
} else if (IsA(bitmapqual, IndexPath)) {
|
|
containGlobalOrLocal = false;
|
|
} else {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
|
|
errmsg("unrecognized node type: %d", nodeTag(bitmapqual))));
|
|
}
|
|
|
|
return containGlobalOrLocal;
|
|
}
|
|
|
|
/*
|
|
* Support partiton index unusable.
|
|
* Check if the index in bitmap heap path is unusable. Contains at least one, return false.
|
|
*/
|
|
bool check_bitmap_heap_path_index_unusable(Path* bitmapqual, RelOptInfo* baserel)
|
|
{
|
|
bool indexUnusable = true;
|
|
if (IsA(bitmapqual, BitmapAndPath)) {
|
|
BitmapAndPath* apath = (BitmapAndPath*)bitmapqual;
|
|
ListCell* l = NULL;
|
|
|
|
foreach (l, apath->bitmapquals) {
|
|
indexUnusable = check_bitmap_heap_path_index_unusable((Path*)lfirst(l), baserel);
|
|
if (!indexUnusable)
|
|
break;
|
|
}
|
|
} else if (IsA(bitmapqual, BitmapOrPath)) {
|
|
BitmapOrPath* opath = (BitmapOrPath*)bitmapqual;
|
|
ListCell* l = NULL;
|
|
|
|
foreach (l, opath->bitmapquals) {
|
|
indexUnusable = check_bitmap_heap_path_index_unusable((Path*)lfirst(l), baserel);
|
|
if (!indexUnusable)
|
|
break;
|
|
}
|
|
} else if (IsA(bitmapqual, IndexPath)) {
|
|
IndexPath* ipath = (IndexPath*)bitmapqual;
|
|
Oid index_oid = ipath->indexinfo->indexoid;
|
|
indexUnusable = checkPartitionIndexUnusable(index_oid, baserel->partItrs, baserel->pruning_result);
|
|
if (!indexUnusable) {
|
|
return indexUnusable;
|
|
}
|
|
} else
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
|
|
errmsg("unrecognized node type: %d", nodeTag(bitmapqual))));
|
|
|
|
return indexUnusable;
|
|
}
|
|
|
|
/*
|
|
* Support partition index unusable.
|
|
* support index/index only scan(b-tree index).
|
|
* Here not support bitmap heap index scan.
|
|
*/
|
|
bool is_partitionIndex_Subpath(Path* subpath)
|
|
{
|
|
bool is_index_path = false;
|
|
|
|
switch (subpath->pathtype) {
|
|
case T_IndexScan:
|
|
case T_IndexOnlyScan:
|
|
is_index_path = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return is_index_path;
|
|
}
|
|
|
|
/* check whether the path is a pwj path */
|
|
bool is_pwj_path(Path* pwjpath)
|
|
{
|
|
bool ret = false;
|
|
if (pwjpath == NULL)
|
|
return ret;
|
|
|
|
if (pwjpath->pathtype == T_PartIterator) {
|
|
Path* subpath = ((PartIteratorPath*)pwjpath)->subPath;
|
|
|
|
if (subpath != NULL) {
|
|
switch (subpath->pathtype) {
|
|
case T_NestLoop:
|
|
case T_MergeJoin:
|
|
case T_HashJoin:
|
|
ret = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* create_index_path
|
|
* Creates a path node for an index scan.
|
|
*
|
|
* 'index' is a usable index.
|
|
* 'indexclauses' is a list of RestrictInfo nodes representing clauses
|
|
* to be used as index qual conditions in the scan.
|
|
* 'indexclausecols' is an integer list of index column numbers (zero based)
|
|
* the indexclauses can be used with.
|
|
* 'indexorderbys' is a list of bare expressions (no RestrictInfos)
|
|
* to be used as index ordering operators in the scan.
|
|
* 'indexorderbycols' is an integer list of index column numbers (zero based)
|
|
* the ordering operators can be used with.
|
|
* 'pathkeys' describes the ordering of the path.
|
|
* 'indexscandir' is ForwardScanDirection or BackwardScanDirection
|
|
* for an ordered index, or NoMovementScanDirection for
|
|
* an unordered index.
|
|
* 'indexonly' is true if an index-only scan is wanted.
|
|
* 'required_outer' is the set of outer relids for a parameterized path.
|
|
* 'loop_count' is the number of repetitions of the indexscan to factor into
|
|
* estimates of caching behavior.
|
|
*
|
|
* Returns the new path node.
|
|
*/
|
|
IndexPath* create_index_path(PlannerInfo* root, IndexOptInfo* index, List* indexclauses, List* indexclausecols,
|
|
List* indexorderbys, List* indexorderbycols, List* pathkeys, ScanDirection indexscandir, bool indexonly,
|
|
Relids required_outer, double loop_count)
|
|
{
|
|
IndexPath* pathnode = makeNode(IndexPath);
|
|
RelOptInfo* rel = index->rel;
|
|
List* indexquals = NIL;
|
|
List* indexqualcols = NIL;
|
|
|
|
pathnode->path.pathtype = indexonly ? T_IndexOnlyScan : T_IndexScan;
|
|
pathnode->path.parent = rel;
|
|
pathnode->path.param_info = get_baserel_parampathinfo(root, rel, required_outer);
|
|
pathnode->path.pathkeys = pathkeys;
|
|
|
|
/* Convert clauses to indexquals the executor can handle */
|
|
expand_indexqual_conditions(index, indexclauses, indexclausecols, &indexquals, &indexqualcols);
|
|
|
|
/* Fill in the pathnode */
|
|
pathnode->indexinfo = index;
|
|
pathnode->indexclauses = indexclauses;
|
|
pathnode->indexquals = indexquals;
|
|
pathnode->indexqualcols = indexqualcols;
|
|
pathnode->indexorderbys = indexorderbys;
|
|
pathnode->indexorderbycols = indexorderbycols;
|
|
pathnode->indexscandir = indexscandir;
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
pathnode->path.distribute_keys = rel->distribute_keys;
|
|
pathnode->path.locator_type = rel->locator_type;
|
|
|
|
/* add location information for index scan path */
|
|
RangeTblEntry* rte = root->simple_rte_array[rel->relid];
|
|
Distribution* distribution = ng_get_baserel_data_distribution(rte->relid, rte->relkind);
|
|
ng_copy_distribution(&pathnode->path.distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
cost_index(pathnode, root, loop_count);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_bitmap_heap_path
|
|
* Creates a path node for a bitmap scan.
|
|
*
|
|
* 'bitmapqual' is a tree of IndexPath, BitmapAndPath, and BitmapOrPath nodes.
|
|
* 'required_outer' is the set of outer relids for a parameterized path.
|
|
* 'loop_count' is the number of repetitions of the indexscan to factor into
|
|
* estimates of caching behavior.
|
|
*
|
|
* loop_count should match the value used when creating the component
|
|
* IndexPaths.
|
|
*/
|
|
BitmapHeapPath* create_bitmap_heap_path(
|
|
PlannerInfo* root, RelOptInfo* rel, Path* bitmapqual, Relids required_outer, double loop_count)
|
|
{
|
|
BitmapHeapPath* pathnode = makeNode(BitmapHeapPath);
|
|
|
|
pathnode->path.pathtype = T_BitmapHeapScan;
|
|
pathnode->path.parent = rel;
|
|
pathnode->path.param_info = get_baserel_parampathinfo(root, rel, required_outer);
|
|
pathnode->path.pathkeys = NIL; /* always unordered */
|
|
|
|
pathnode->bitmapqual = bitmapqual;
|
|
|
|
cost_bitmap_heap_scan(&pathnode->path, root, rel, pathnode->path.param_info, bitmapqual, loop_count);
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
pathnode->path.distribute_keys = rel->distribute_keys;
|
|
pathnode->path.locator_type = rel->locator_type;
|
|
|
|
/* add location information for bitmap heap path */
|
|
RangeTblEntry* rte = root->simple_rte_array[rel->relid];
|
|
Distribution* distribution = ng_get_baserel_data_distribution(rte->relid, rte->relkind);
|
|
ng_copy_distribution(&pathnode->path.distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_bitmap_and_path
|
|
* Creates a path node representing a BitmapAnd.
|
|
*/
|
|
BitmapAndPath* create_bitmap_and_path(PlannerInfo* root, RelOptInfo* rel, List* bitmapquals)
|
|
{
|
|
BitmapAndPath* pathnode = makeNode(BitmapAndPath);
|
|
|
|
pathnode->path.pathtype = T_BitmapAnd;
|
|
pathnode->path.parent = rel;
|
|
pathnode->path.param_info = NULL; /* not used in bitmap trees */
|
|
pathnode->path.pathkeys = NIL; /* always unordered */
|
|
|
|
pathnode->bitmapquals = bitmapquals;
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
pathnode->path.distribute_keys = rel->distribute_keys;
|
|
pathnode->path.locator_type = rel->locator_type;
|
|
|
|
/* add location information for bitmap and path */
|
|
RangeTblEntry* rte = root->simple_rte_array[rel->relid];
|
|
Distribution* distribution = ng_get_baserel_data_distribution(rte->relid, rte->relkind);
|
|
ng_copy_distribution(&pathnode->path.distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
/* this sets bitmapselectivity as well as the regular cost fields: */
|
|
cost_bitmap_and_node(pathnode, root);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_bitmap_or_path
|
|
* Creates a path node representing a BitmapOr.
|
|
*/
|
|
BitmapOrPath* create_bitmap_or_path(PlannerInfo* root, RelOptInfo* rel, List* bitmapquals)
|
|
{
|
|
BitmapOrPath* pathnode = makeNode(BitmapOrPath);
|
|
|
|
pathnode->path.pathtype = T_BitmapOr;
|
|
pathnode->path.parent = rel;
|
|
pathnode->path.param_info = NULL; /* not used in bitmap trees */
|
|
pathnode->path.pathkeys = NIL; /* always unordered */
|
|
|
|
pathnode->bitmapquals = bitmapquals;
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
pathnode->path.distribute_keys = rel->distribute_keys;
|
|
pathnode->path.locator_type = rel->locator_type;
|
|
|
|
/* add location information for bitmap or path */
|
|
RangeTblEntry* rte = root->simple_rte_array[rel->relid];
|
|
Distribution* distribution = ng_get_baserel_data_distribution(rte->relid, rte->relkind);
|
|
ng_copy_distribution(&pathnode->path.distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
/* this sets bitmapselectivity as well as the regular cost fields: */
|
|
cost_bitmap_or_node(pathnode, root);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_tidscan_path
|
|
* Creates a path corresponding to a scan by TID, returning the pathnode.
|
|
*/
|
|
TidPath* create_tidscan_path(PlannerInfo* root, RelOptInfo* rel, List* tidquals)
|
|
{
|
|
TidPath* pathnode = makeNode(TidPath);
|
|
|
|
pathnode->path.pathtype = T_TidScan;
|
|
pathnode->path.parent = rel;
|
|
pathnode->path.param_info = NULL; /* never parameterized at present */
|
|
pathnode->path.pathkeys = NIL; /* always unordered */
|
|
|
|
pathnode->tidquals = tidquals;
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
pathnode->path.distribute_keys = rel->distribute_keys;
|
|
pathnode->path.locator_type = rel->locator_type;
|
|
|
|
/* add location information for TID scan path */
|
|
RangeTblEntry* rte = root->simple_rte_array[rel->relid];
|
|
Distribution* distribution = ng_get_baserel_data_distribution(rte->relid, rte->relkind);
|
|
ng_copy_distribution(&pathnode->path.distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
cost_tidscan(&pathnode->path, root, rel, tidquals);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_append_path
|
|
* Creates a path corresponding to an Append plan, returning the
|
|
* pathnode.
|
|
*
|
|
* Note that we must handle subpaths = NIL, representing a dummy access path.
|
|
*/
|
|
AppendPath* create_append_path(PlannerInfo* root, RelOptInfo* rel, List* subpaths, Relids required_outer)
|
|
{
|
|
AppendPath* pathnode = makeNode(AppendPath);
|
|
ListCell* l = NULL;
|
|
double local_rows = 0;
|
|
|
|
pathnode->path.pathtype = T_Append;
|
|
pathnode->path.parent = rel;
|
|
pathnode->path.param_info = get_appendrel_parampathinfo(rel, required_outer);
|
|
pathnode->path.pathkeys = NIL; /* result is always considered
|
|
* unsorted */
|
|
pathnode->subpaths = subpaths;
|
|
|
|
/*
|
|
* We don't bother with inventing a cost_append(), but just do it here.
|
|
*
|
|
* Compute rows and costs as sums of subplan rows and 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. If you change this, see also make_append().
|
|
*/
|
|
set_path_rows(&pathnode->path, 0, rel->multiple);
|
|
pathnode->path.startup_cost = 0;
|
|
pathnode->path.total_cost = 0;
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
/*
|
|
* This function can alter subpaths's rows, AppendPath's rows rely on it.
|
|
* So this function need be in advance.
|
|
*/
|
|
mark_append_path(root, rel, (Path*)pathnode, subpaths);
|
|
} else {
|
|
pathnode->path.distribute_keys = rel->distribute_keys;
|
|
pathnode->path.locator_type = rel->locator_type;
|
|
Distribution* distribution = ng_get_default_computing_group_distribution();
|
|
ng_copy_distribution(&pathnode->path.distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
bool all_parallelized = true;
|
|
|
|
/*
|
|
* Handle the HDFS scan situation.
|
|
*/
|
|
if (2 == list_length(subpaths)) {
|
|
Path* p1 = (Path*)linitial(subpaths);
|
|
Path* p2 = (Path*)lsecond(subpaths);
|
|
if (p1->pathtype == T_DfsScan && p2->pathtype == T_SeqScan) {
|
|
if (p1->dop > 1) {
|
|
all_parallelized = true;
|
|
p2->dop = p1->dop;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if all the subpaths already paralleled,
|
|
* then we can parallel the append path.
|
|
* Otherwise, we need to add local gather
|
|
* above the parallelized subpaths.
|
|
*/
|
|
foreach (l, subpaths) {
|
|
Path* subpath = (Path*)lfirst(l);
|
|
if (subpath->dop <= 1)
|
|
all_parallelized = false;
|
|
}
|
|
|
|
if (subpaths != NULL && all_parallelized)
|
|
pathnode->path.dop = u_sess->opt_cxt.query_dop;
|
|
else
|
|
pathnode->path.dop = 1;
|
|
|
|
foreach (l, subpaths) {
|
|
Path* subpath = (Path*)lfirst(l);
|
|
|
|
local_rows += PATH_LOCAL_ROWS(subpath);
|
|
pathnode->path.rows += subpath->rows;
|
|
|
|
/*
|
|
* Add local gather above the parallelized subpath.
|
|
* Do not allow adding stream path where current subpath was parameterized.
|
|
*/
|
|
if (subpath->dop > 1 && !all_parallelized) {
|
|
if (subpath->param_info) {
|
|
/* free memory before return NULL */
|
|
pathnode->path.parent = NULL;
|
|
/*
|
|
* There could be a new papraminfo build in get_appendrel_parampathinfo(),
|
|
* but it is unneccessary to worry about the memory leak as we will free it
|
|
* after all by reseting OptimizerContext.
|
|
*/
|
|
pathnode->path.param_info = NULL;
|
|
pathnode->subpaths = NIL;
|
|
pathnode->path.distribute_keys = NIL;
|
|
bms_free_ext(pathnode->path.distribution.bms_data_nodeids);
|
|
pfree_ext(pathnode);
|
|
pathnode = NULL;
|
|
return pathnode;
|
|
}
|
|
|
|
if (IsA(subpath, StreamPath)) {
|
|
StreamPath* stream = (StreamPath*)subpath;
|
|
stream->smpDesc->consumerDop = 1;
|
|
} else {
|
|
ParallelDesc* smp_desc = create_smpDesc(1, subpath->dop, LOCAL_ROUNDROBIN);
|
|
subpath = create_stream_path(
|
|
root, subpath->parent, STREAM_REDISTRIBUTE, NIL, NIL, subpath, 1.0, NULL, smp_desc);
|
|
lfirst(l) = (void*)subpath;
|
|
}
|
|
}
|
|
|
|
if (l == list_head(subpaths)) /* first node? */
|
|
pathnode->path.startup_cost = subpath->startup_cost;
|
|
pathnode->path.total_cost += subpath->total_cost;
|
|
pathnode->path.stream_cost += subpath->stream_cost;
|
|
|
|
/* All child paths must have same parameterization */
|
|
AssertEreport(bms_equal(PATH_REQ_OUTER(subpath), required_outer),
|
|
MOD_OPT_JOIN,
|
|
"All child paths must have same parameterization");
|
|
}
|
|
|
|
/* DFS relation scan */
|
|
if (rel->rtekind == RTE_RELATION && pathnode->path.param_info == NULL) {
|
|
/* Set dfs base rel rows, rel rows can be change when include rows hint. */
|
|
pathnode->path.rows = rel->rows;
|
|
}
|
|
|
|
/* Calculate overal multiple for append path */
|
|
if (pathnode->path.rows != 0)
|
|
pathnode->path.multiple = local_rows / pathnode->path.rows * ng_get_dest_num_data_nodes((Path*)pathnode);
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_merge_append_path
|
|
* Creates a path corresponding to a MergeAppend plan, returning the
|
|
* pathnode.
|
|
*/
|
|
MergeAppendPath* create_merge_append_path(
|
|
PlannerInfo* root, RelOptInfo* rel, List* subpaths, List* pathkeys, Relids required_outer)
|
|
{
|
|
MergeAppendPath* pathnode = makeNode(MergeAppendPath);
|
|
Cost input_startup_cost;
|
|
Cost input_total_cost;
|
|
Cost input_stream_cost;
|
|
ListCell* l = NULL;
|
|
|
|
pathnode->path.pathtype = T_MergeAppend;
|
|
pathnode->path.parent = rel;
|
|
pathnode->path.param_info = get_appendrel_parampathinfo(rel, required_outer);
|
|
pathnode->path.pathkeys = pathkeys;
|
|
pathnode->subpaths = subpaths;
|
|
|
|
/*
|
|
* Apply query-wide LIMIT if known and path is for sole base relation.
|
|
* (Handling this at this low level is a bit klugy.)
|
|
*/
|
|
if (bms_equal(rel->relids, root->all_baserels))
|
|
pathnode->limit_tuples = root->limit_tuples;
|
|
else
|
|
pathnode->limit_tuples = -1.0;
|
|
|
|
/*
|
|
* Add up the sizes and costs of the input paths.
|
|
*/
|
|
set_path_rows(&pathnode->path, 0, rel->multiple);
|
|
input_startup_cost = 0;
|
|
input_total_cost = 0;
|
|
input_stream_cost = 0;
|
|
pathnode->mem_info = (OpMemInfo*)palloc0(sizeof(OpMemInfo) * list_length(subpaths));
|
|
int i = 0;
|
|
foreach (l, subpaths) {
|
|
Path* subpath = (Path*)lfirst(l);
|
|
/*
|
|
* For correlated subplan, there will be a broadcast added later,
|
|
* so make the righ estimation of rows beforehand
|
|
*/
|
|
bool needbroadcast = root->is_correlated && !is_replicated_path(subpath);
|
|
|
|
pathnode->path.rows += subpath->rows;
|
|
|
|
if (pathkeys_contained_in(pathkeys, subpath->pathkeys) && !needbroadcast) {
|
|
/* Subpath is adequately ordered, we won't need to sort it */
|
|
input_startup_cost += subpath->startup_cost;
|
|
input_total_cost += subpath->total_cost;
|
|
input_stream_cost += subpath->stream_cost;
|
|
} else {
|
|
/* We'll need to insert a Sort node, so include cost for that */
|
|
Path sort_path; /* dummy for result of cost_sort */
|
|
int subpath_width = get_path_actual_total_width(subpath, root->glob->vectorized, OP_SORT);
|
|
|
|
cost_sort(&sort_path,
|
|
pathkeys,
|
|
subpath->total_cost,
|
|
needbroadcast ? subpath->parent->tuples : RELOPTINFO_LOCAL_FIELD(root, subpath->parent, tuples),
|
|
subpath_width,
|
|
0.0,
|
|
u_sess->opt_cxt.op_work_mem,
|
|
pathnode->limit_tuples,
|
|
root->glob->vectorized,
|
|
1,
|
|
&pathnode->mem_info[i]);
|
|
input_startup_cost += sort_path.startup_cost;
|
|
input_total_cost += sort_path.total_cost;
|
|
input_stream_cost += sort_path.stream_cost;
|
|
}
|
|
|
|
/* All child paths must have same parameterization */
|
|
AssertEreport(bms_equal(PATH_REQ_OUTER(subpath), required_outer),
|
|
MOD_OPT_JOIN,
|
|
"All child paths must have same parameterization");
|
|
i++;
|
|
}
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
mark_append_path(root, rel, (Path*)pathnode, subpaths);
|
|
} else {
|
|
pathnode->path.distribute_keys = rel->distribute_keys;
|
|
pathnode->path.locator_type = rel->locator_type;
|
|
Distribution* distribution = ng_get_default_computing_group_distribution();
|
|
ng_copy_distribution(&pathnode->path.distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
/* Now we can compute total costs of the MergeAppend */
|
|
cost_merge_append(&pathnode->path,
|
|
root,
|
|
pathkeys,
|
|
list_length(subpaths),
|
|
input_startup_cost,
|
|
input_total_cost,
|
|
get_local_rows(rel->tuples,
|
|
rel->multiple,
|
|
IsLocatorReplicated(rel->locator_type),
|
|
ng_get_dest_num_data_nodes(&pathnode->path)));
|
|
pathnode->path.stream_cost = input_stream_cost;
|
|
|
|
foreach (l, subpaths) {
|
|
Path* subpath = (Path*)lfirst(l);
|
|
|
|
if (subpath->dop > 1) {
|
|
/* Do not allow adding stream path where current subpath was parameterized. */
|
|
if (subpath->param_info) {
|
|
/* free memory before return NULL */
|
|
pathnode->path.parent = NULL;
|
|
/*
|
|
* There could be a new papraminfo build in get_appendrel_parampathinfo(),
|
|
* but it is unneccessary to worry about the memory leak as we will free it
|
|
* after all by reseting OptimizerContext.
|
|
*/
|
|
pathnode->path.param_info = NULL;
|
|
pathnode->subpaths = NIL;
|
|
pathnode->path.distribute_keys = NIL;
|
|
bms_free_ext(pathnode->path.distribution.bms_data_nodeids);
|
|
pfree_ext(pathnode);
|
|
pathnode = NULL;
|
|
return pathnode;
|
|
}
|
|
|
|
/* Add local gather above the parallelized subpath. */
|
|
if (IsA(subpath, StreamPath)) {
|
|
StreamPath* stream = (StreamPath*)subpath;
|
|
stream->smpDesc->consumerDop = 1;
|
|
} else {
|
|
ParallelDesc* smp_desc = create_smpDesc(1, subpath->dop, LOCAL_ROUNDROBIN);
|
|
subpath = create_stream_path(root,
|
|
subpath->parent,
|
|
STREAM_REDISTRIBUTE,
|
|
subpath->distribute_keys,
|
|
NIL,
|
|
subpath,
|
|
1.0,
|
|
NULL,
|
|
smp_desc);
|
|
lfirst(l) = (void*)subpath;
|
|
}
|
|
/* All child paths must have same parameterization */
|
|
AssertEreport(bms_equal(PATH_REQ_OUTER(subpath), required_outer),
|
|
MOD_OPT_JOIN,
|
|
"All child paths must have same parameterization");
|
|
}
|
|
}
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_result_path
|
|
* Creates a path representing a Result-and-nothing-else plan.
|
|
* This is only used for the case of a query with an empty jointree.
|
|
*/
|
|
ResultPath* create_result_path(List* quals, Path* subpath)
|
|
{
|
|
ResultPath* pathnode = makeNode(ResultPath);
|
|
|
|
pathnode->path.pathtype = T_BaseResult;
|
|
pathnode->path.param_info = NULL;
|
|
pathnode->path.pathkeys = NIL;
|
|
pathnode->subpath = subpath;
|
|
if (subpath != NULL) {
|
|
pathnode->path.param_info = subpath->param_info;
|
|
pathnode->path.pathkeys = subpath->pathkeys;
|
|
pathnode->path.parent = subpath->parent;
|
|
pathnode->pathqual = quals;
|
|
set_path_rows(&pathnode->path, clamp_row_est(Max(subpath->rows * DEFAULT_EQ_SEL, 1)));
|
|
pathnode->path.startup_cost = subpath->startup_cost;
|
|
pathnode->path.total_cost = subpath->total_cost;
|
|
pathnode->path.dop = subpath->dop;
|
|
pathnode->path.stream_cost = subpath->stream_cost;
|
|
#ifdef STREAMPLAN
|
|
/* result path will inherit node group and distribute information from it's child node */
|
|
inherit_path_locator_info((Path*)pathnode, subpath);
|
|
#endif
|
|
} else {
|
|
pathnode->path.parent = NULL;
|
|
pathnode->quals = quals;
|
|
/* Hardly worth defining a cost_result() function ... just do it */
|
|
set_path_rows(&pathnode->path, 1, 1);
|
|
pathnode->path.startup_cost = 0;
|
|
pathnode->path.total_cost = u_sess->attr.attr_sql.cpu_tuple_cost;
|
|
pathnode->path.stream_cost = 0;
|
|
|
|
Distribution* distribution = ng_get_default_computing_group_distribution();
|
|
ng_set_distribution(&pathnode->path.distribution, distribution);
|
|
}
|
|
|
|
/*
|
|
* In theory we should include the qual eval cost as well, but at present
|
|
* that doesn't accomplish much except duplicate work that will be done
|
|
* again in make_result; since this is only used for degenerate cases,
|
|
* nothing interesting will be done with the path cost values...
|
|
*/
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_material_path
|
|
* Creates a path corresponding to a Material plan, returning the
|
|
* pathnode.
|
|
*/
|
|
MaterialPath* create_material_path(Path* subpath, bool materialize_all)
|
|
{
|
|
MaterialPath* pathnode = makeNode(MaterialPath);
|
|
double input_global_rows = subpath->rows;
|
|
RelOptInfo* rel = subpath->parent;
|
|
|
|
pathnode->path.pathtype = T_Material;
|
|
pathnode->path.parent = rel;
|
|
pathnode->path.param_info = subpath->param_info;
|
|
pathnode->path.pathkeys = subpath->pathkeys;
|
|
pathnode->path.dop = subpath->dop;
|
|
pathnode->materialize_all = materialize_all;
|
|
|
|
#ifdef STREAMPLAN
|
|
/* material path will inherit node group and distribute information from it's child node */
|
|
inherit_path_locator_info((Path*)pathnode, subpath);
|
|
#endif
|
|
|
|
pathnode->subpath = subpath;
|
|
set_path_rows(&pathnode->path, input_global_rows, subpath->multiple);
|
|
|
|
cost_material(&pathnode->path, subpath->startup_cost, subpath->total_cost, PATH_LOCAL_ROWS(subpath), rel->width);
|
|
pathnode->path.stream_cost = subpath->stream_cost;
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_unique_path
|
|
* Creates a path representing elimination of distinct rows from the
|
|
* input data. Distinct-ness is defined according to the needs of the
|
|
* semijoin represented by sjinfo. If it is not possible to identify
|
|
* how to make the data unique, NULL is returned.
|
|
*
|
|
* If used at all, this is likely to be called repeatedly on the same rel;
|
|
* and the input subpath should always be the same (the cheapest_total path
|
|
* for the rel). So we cache the result.
|
|
*/
|
|
UniquePath* create_unique_path(PlannerInfo* root, RelOptInfo* rel, Path* subpath, SpecialJoinInfo* sjinfo)
|
|
{
|
|
UniquePath* pathnode = NULL;
|
|
Path sort_path; /* dummy for result of cost_sort */
|
|
Path agg_path; /* dummy for result of cost_agg */
|
|
MemoryContext oldcontext;
|
|
List* in_operators = NIL;
|
|
List* uniq_exprs = NIL;
|
|
bool all_btree = false;
|
|
bool all_hash = false;
|
|
int numCols;
|
|
ListCell* lc = NULL;
|
|
double local_rows, num_groups;
|
|
OpMemInfo sort_mem_info, hash_mem_info;
|
|
errno_t rc = 0;
|
|
|
|
rc = memset_s(&sort_path, sizeof(Path), 0, sizeof(Path));
|
|
securec_check(rc, "\0", "\0");
|
|
rc = memset_s(&agg_path, sizeof(Path), 0, sizeof(Path));
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
/* Caller made a mistake if subpath isn't cheapest_total ... */
|
|
foreach (lc, rel->cheapest_total_path) {
|
|
if (subpath == lfirst(lc))
|
|
break;
|
|
}
|
|
AssertEreport(lc != NULL, MOD_OPT_JOIN, "Subpath should be one of cheapest total path of rel");
|
|
AssertEreport(subpath->parent == rel || subpath->parent->base_rel == rel, MOD_OPT_JOIN, "");
|
|
/* ... or if SpecialJoinInfo is the wrong one */
|
|
AssertEreport(sjinfo->jointype == JOIN_SEMI, MOD_OPT_JOIN, "Join type should be semi join");
|
|
AssertEreport(
|
|
bms_equal(rel->relids, sjinfo->syn_righthand), MOD_OPT_JOIN, "All relids should be within join right hand");
|
|
|
|
/* If result already cached, return it */
|
|
if (rel->cheapest_unique_path)
|
|
return (UniquePath*)rel->cheapest_unique_path;
|
|
|
|
/* If we previously failed, return NULL quickly */
|
|
if (sjinfo->join_quals == NIL)
|
|
return NULL;
|
|
|
|
/*
|
|
* We must ensure path struct and subsidiary data are allocated in main
|
|
* planning context; otherwise GEQO memory management causes trouble.
|
|
*/
|
|
oldcontext = MemoryContextSwitchTo(root->planner_cxt);
|
|
|
|
/* ----------
|
|
* Look to see whether the semijoin's join quals consist of AND'ed
|
|
* equality operators, with (only) RHS variables on only one side of
|
|
* each one. If so, we can figure out how to enforce uniqueness for
|
|
* the RHS.
|
|
*
|
|
* Note that the input join_quals list is the list of quals that are
|
|
* *syntactically* associated with the semijoin, which in practice means
|
|
* the synthesized comparison list for an IN or the WHERE of an EXISTS.
|
|
* Particularly in the latter case, it might contain clauses that aren't
|
|
* *semantically* associated with the join, but refer to just one side or
|
|
* the other. We can ignore such clauses here, as they will just drop
|
|
* down to be processed within one side or the other. (It is okay to
|
|
* consider only the syntactically-associated clauses here because for a
|
|
* semijoin, no higher-level quals could refer to the RHS, and so there
|
|
* can be no other quals that are semantically associated with this join.
|
|
* We do things this way because it is useful to be able to run this test
|
|
* before we have extracted the list of quals that are actually
|
|
* semantically associated with the particular join.)
|
|
*
|
|
* Note that the in_operators list consists of the joinqual operators
|
|
* themselves (but commuted if needed to put the RHS value on the right).
|
|
* These could be cross-type operators, in which case the operator
|
|
* actually needed for uniqueness is a related single-type operator.
|
|
* We assume here that that operator will be available from the btree
|
|
* or hash opclass when the time comes ... if not, create_unique_plan()
|
|
* will fail.
|
|
* ----------
|
|
*/
|
|
in_operators = NIL;
|
|
uniq_exprs = NIL;
|
|
all_btree = true;
|
|
all_hash = u_sess->attr.attr_sql.enable_hashagg; /* don't consider hash if not enabled */
|
|
foreach (lc, sjinfo->join_quals) {
|
|
OpExpr* op = (OpExpr*)lfirst(lc);
|
|
Oid opno;
|
|
Node* left_expr = NULL;
|
|
Node* right_expr = NULL;
|
|
Relids left_varnos;
|
|
Relids right_varnos;
|
|
Relids all_varnos;
|
|
Oid opinputtype;
|
|
|
|
/* Is it a binary opclause? */
|
|
if (!IsA(op, OpExpr) || list_length(op->args) != 2) {
|
|
/* No, but does it reference both sides? */
|
|
all_varnos = pull_varnos((Node*)op);
|
|
if (!bms_overlap(all_varnos, sjinfo->syn_righthand) || bms_is_subset(all_varnos, sjinfo->syn_righthand)) {
|
|
/*
|
|
* Clause refers to only one rel, so ignore it --- unless it
|
|
* contains volatile functions, in which case we'd better
|
|
* punt.
|
|
*/
|
|
if (contain_volatile_functions((Node*)op))
|
|
goto no_unique_path;
|
|
continue;
|
|
}
|
|
/* Non-operator clause referencing both sides, must punt */
|
|
goto no_unique_path;
|
|
}
|
|
|
|
/* Extract data from binary opclause */
|
|
opno = op->opno;
|
|
left_expr = (Node*)linitial(op->args);
|
|
right_expr = (Node*)lsecond(op->args);
|
|
left_varnos = pull_varnos(left_expr);
|
|
right_varnos = pull_varnos(right_expr);
|
|
all_varnos = bms_union(left_varnos, right_varnos);
|
|
opinputtype = exprType(left_expr);
|
|
|
|
/* Does it reference both sides? */
|
|
if (!bms_overlap(all_varnos, sjinfo->syn_righthand) || bms_is_subset(all_varnos, sjinfo->syn_righthand)) {
|
|
/*
|
|
* Clause refers to only one rel, so ignore it --- unless it
|
|
* contains volatile functions, in which case we'd better punt.
|
|
*/
|
|
if (contain_volatile_functions((Node*)op))
|
|
goto no_unique_path;
|
|
continue;
|
|
}
|
|
|
|
/* check rel membership of arguments */
|
|
if (!bms_is_empty(right_varnos) && bms_is_subset(right_varnos, sjinfo->syn_righthand) &&
|
|
!bms_overlap(left_varnos, sjinfo->syn_righthand)) {
|
|
/* typical case, right_expr is RHS variable */
|
|
} else if (!bms_is_empty(left_varnos) && bms_is_subset(left_varnos, sjinfo->syn_righthand) &&
|
|
!bms_overlap(right_varnos, sjinfo->syn_righthand)) {
|
|
/* flipped case, left_expr is RHS variable */
|
|
opno = get_commutator(opno);
|
|
if (!OidIsValid(opno))
|
|
goto no_unique_path;
|
|
right_expr = left_expr;
|
|
} else
|
|
goto no_unique_path;
|
|
|
|
/* all operators must be btree equality or hash equality */
|
|
if (all_btree) {
|
|
/* oprcanmerge is considered a hint... */
|
|
if (!op_mergejoinable(opno, opinputtype) || get_mergejoin_opfamilies(opno) == NIL)
|
|
all_btree = false;
|
|
}
|
|
if (all_hash) {
|
|
/* ... but oprcanhash had better be correct */
|
|
if (!op_hashjoinable(opno, opinputtype))
|
|
all_hash = false;
|
|
}
|
|
if (!(all_btree || all_hash))
|
|
goto no_unique_path;
|
|
|
|
/* so far so good, keep building lists */
|
|
in_operators = lappend_oid(in_operators, opno);
|
|
uniq_exprs = lappend(uniq_exprs, copyObject(right_expr));
|
|
}
|
|
|
|
/* Punt if we didn't find at least one column to unique-ify */
|
|
if (uniq_exprs == NIL)
|
|
goto no_unique_path;
|
|
|
|
/*
|
|
* The expressions we'd need to unique-ify mustn't be volatile.
|
|
*/
|
|
if (contain_volatile_functions((Node*)uniq_exprs))
|
|
goto no_unique_path;
|
|
|
|
/*
|
|
* If we get here, we can unique-ify using at least one of sorting and
|
|
* hashing. Start building the result Path object.
|
|
*/
|
|
pathnode = makeNode(UniquePath);
|
|
pathnode->path.pathtype = T_Unique;
|
|
pathnode->path.parent = rel;
|
|
pathnode->path.param_info = subpath->param_info;
|
|
pathnode->path.dop = subpath->dop;
|
|
|
|
/*
|
|
* Assume the output is unsorted, since we don't necessarily have pathkeys
|
|
* to represent it. (This might get overridden below.)
|
|
*/
|
|
pathnode->path.pathkeys = NIL;
|
|
|
|
pathnode->subpath = subpath;
|
|
pathnode->in_operators = in_operators;
|
|
pathnode->uniq_exprs = uniq_exprs;
|
|
pathnode->both_method = false;
|
|
pathnode->hold_tlist = false;
|
|
|
|
#ifdef STREAMPLAN
|
|
inherit_path_locator_info((Path*)pathnode, subpath);
|
|
#endif
|
|
|
|
/*
|
|
* If the input is a relation and it has a unique index that proves the
|
|
* uniq_exprs are unique, then we don't need to do anything. Note that
|
|
* relation_has_unique_index_for automatically considers restriction
|
|
* clauses for the rel, as well.
|
|
*/
|
|
if (rel->rtekind == RTE_RELATION && all_btree &&
|
|
relation_has_unique_index_for(root, rel, NIL, uniq_exprs, in_operators)) {
|
|
pathnode->umethod = UNIQUE_PATH_NOOP;
|
|
set_path_rows(&pathnode->path, rel->rows, subpath->multiple);
|
|
pathnode->path.startup_cost = subpath->startup_cost;
|
|
pathnode->path.total_cost = subpath->total_cost;
|
|
pathnode->path.stream_cost = subpath->stream_cost;
|
|
pathnode->path.pathkeys = subpath->pathkeys;
|
|
|
|
rel->cheapest_unique_path = (Path*)pathnode;
|
|
|
|
(void)MemoryContextSwitchTo(oldcontext);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* If the input is a subquery whose output must be unique already, then we
|
|
* don't need to do anything. The test for uniqueness has to consider
|
|
* exactly which columns we are extracting; for example "SELECT DISTINCT
|
|
* x,y" doesn't guarantee that x alone is distinct. So we cannot check for
|
|
* this optimization unless uniq_exprs consists only of simple Vars
|
|
* referencing subquery outputs. (Possibly we could do something with
|
|
* expressions in the subquery outputs, too, but for now keep it simple.)
|
|
*/
|
|
if (rel->rtekind == RTE_SUBQUERY) {
|
|
RangeTblEntry* rte = planner_rt_fetch(rel->relid, root);
|
|
|
|
if (query_supports_distinctness(rte->subquery)) {
|
|
List* sub_tlist_colnos = translate_sub_tlist(uniq_exprs, rel->relid);
|
|
|
|
if (sub_tlist_colnos != NIL && query_is_distinct_for(rte->subquery, sub_tlist_colnos, in_operators)) {
|
|
pathnode->umethod = UNIQUE_PATH_NOOP;
|
|
pathnode->path.rows = rel->rows;
|
|
pathnode->path.startup_cost = subpath->startup_cost;
|
|
pathnode->path.total_cost = subpath->total_cost;
|
|
pathnode->path.pathkeys = subpath->pathkeys;
|
|
|
|
rel->cheapest_unique_path = (Path*)pathnode;
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
return pathnode;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Estimate number of output rows */
|
|
local_rows = RELOPTINFO_LOCAL_FIELD(root, rel, rows);
|
|
num_groups =
|
|
estimate_num_groups(root, uniq_exprs, local_rows, ng_get_dest_num_data_nodes(root, rel), STATS_TYPE_LOCAL);
|
|
pathnode->path.rows = Min(get_global_rows(num_groups, 1.0, ng_get_dest_num_data_nodes((Path*)pathnode)), rel->rows);
|
|
if (pathnode->path.rows != 0)
|
|
pathnode->path.multiple = num_groups / pathnode->path.rows * ng_get_dest_num_data_nodes((Path*)pathnode);
|
|
numCols = list_length(uniq_exprs);
|
|
|
|
rc = memset_s(&sort_mem_info, sizeof(OpMemInfo), 0, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
rc = memset_s(&hash_mem_info, sizeof(OpMemInfo), 0, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
if (all_btree) {
|
|
int subpath_width = get_path_actual_total_width(subpath, root->glob->vectorized, OP_SORT);
|
|
|
|
/*
|
|
* Estimate cost for sort+unique implementation
|
|
*/
|
|
cost_sort(&sort_path,
|
|
NIL,
|
|
subpath->total_cost,
|
|
local_rows,
|
|
subpath_width,
|
|
0.0,
|
|
u_sess->opt_cxt.op_work_mem,
|
|
-1.0,
|
|
root->glob->vectorized,
|
|
1,
|
|
&sort_mem_info);
|
|
|
|
/*
|
|
* 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.) This should agree with
|
|
* make_unique.
|
|
*/
|
|
sort_path.total_cost +=
|
|
u_sess->attr.attr_sql.cpu_operator_cost * RELOPTINFO_LOCAL_FIELD(root, rel, rows) * numCols;
|
|
}
|
|
|
|
if (all_hash) {
|
|
Size hashentrysize = 0;
|
|
|
|
if (root->glob->vectorized)
|
|
hashentrysize = get_path_actual_total_width(subpath, root->glob->vectorized, OP_HASHAGG, 0);
|
|
else
|
|
hashentrysize = get_hash_entry_size(rel->width);
|
|
|
|
Distribution* distribution = ng_get_dest_distribution((Path*)pathnode);
|
|
ng_copy_distribution(&agg_path.distribution, distribution);
|
|
cost_agg(&agg_path,
|
|
root,
|
|
AGG_HASHED,
|
|
NULL,
|
|
numCols,
|
|
num_groups,
|
|
subpath->startup_cost,
|
|
subpath->total_cost,
|
|
local_rows,
|
|
rel->width,
|
|
hashentrysize,
|
|
1,
|
|
&hash_mem_info);
|
|
}
|
|
|
|
if (all_btree && all_hash) {
|
|
if (agg_path.total_cost < sort_path.total_cost)
|
|
pathnode->umethod = UNIQUE_PATH_HASH;
|
|
else
|
|
pathnode->umethod = UNIQUE_PATH_SORT;
|
|
|
|
pathnode->both_method = true;
|
|
} else if (all_btree) {
|
|
pathnode->umethod = UNIQUE_PATH_SORT;
|
|
} else if (all_hash) {
|
|
pathnode->umethod = UNIQUE_PATH_HASH;
|
|
} else {
|
|
goto no_unique_path;
|
|
}
|
|
|
|
if (pathnode->umethod == UNIQUE_PATH_HASH) {
|
|
pathnode->path.startup_cost = agg_path.startup_cost;
|
|
pathnode->path.total_cost = agg_path.total_cost;
|
|
rc = memcpy_s(&pathnode->mem_info, sizeof(OpMemInfo), &hash_mem_info, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
} else {
|
|
pathnode->path.startup_cost = sort_path.startup_cost;
|
|
pathnode->path.total_cost = sort_path.total_cost;
|
|
rc = memcpy_s(&pathnode->mem_info, sizeof(OpMemInfo), &sort_mem_info, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
|
|
pathnode->path.stream_cost = subpath->stream_cost;
|
|
rel->cheapest_unique_path = (Path*)pathnode;
|
|
|
|
(void)MemoryContextSwitchTo(oldcontext);
|
|
|
|
return pathnode;
|
|
|
|
no_unique_path: /* failure exit */
|
|
|
|
/* Mark the SpecialJoinInfo as not unique-able */
|
|
sjinfo->join_quals = NIL;
|
|
|
|
(void)MemoryContextSwitchTo(oldcontext);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* translate_sub_tlist - get subquery column numbers represented by tlist
|
|
*
|
|
* The given targetlist usually contains only Vars referencing the given relid.
|
|
* Extract their varattnos (ie, the column numbers of the subquery) and return
|
|
* as an integer List.
|
|
*
|
|
* If any of the tlist items is not a simple Var, we cannot determine whether
|
|
* the subquery's uniqueness condition (if any) matches ours, so punt and
|
|
* return NIL.
|
|
*/
|
|
static List* translate_sub_tlist(List* tlist, int relid)
|
|
{
|
|
List* result = NIL;
|
|
ListCell* l = NULL;
|
|
|
|
foreach (l, tlist) {
|
|
Var* var = (Var*)lfirst(l);
|
|
|
|
if (var == NULL || !IsA(var, Var) || var->varno != (unsigned int)relid)
|
|
return NIL; /* punt */
|
|
|
|
result = lappend_int(result, var->varattno);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* create_subqueryscan_path
|
|
* Creates a path corresponding to a sequential scan of a subquery,
|
|
* returning the pathnode.
|
|
*/
|
|
Path* create_subqueryscan_path(PlannerInfo* root, RelOptInfo* rel, List* pathkeys, Relids required_outer)
|
|
{
|
|
Path* pathnode = makeNode(Path);
|
|
|
|
pathnode->pathtype = T_SubqueryScan;
|
|
pathnode->parent = rel;
|
|
pathnode->param_info = get_baserel_parampathinfo(root, rel, required_outer);
|
|
pathnode->pathkeys = pathkeys;
|
|
|
|
cost_subqueryscan(pathnode, root, rel, pathnode->param_info);
|
|
|
|
/* reset distribute keys ,set it later. */
|
|
list_free_ext(rel->distribute_keys);
|
|
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN) {
|
|
Plan* subplan = rel->subplan;
|
|
if (subplan->dop > 1)
|
|
pathnode->dop = subplan->dop;
|
|
else
|
|
pathnode->dop = 1;
|
|
|
|
if (is_execute_on_datanodes(subplan)) {
|
|
if (is_replicated_plan(subplan)) {
|
|
rel->distribute_keys = NULL;
|
|
rel->locator_type = LOCATOR_TYPE_REPLICATED;
|
|
} else if (is_hashed_plan(subplan)) {
|
|
List* distribute_index =
|
|
distributeKeyIndex(rel->subroot, subplan->distributed_keys, subplan->targetlist);
|
|
if (distribute_index == NIL) {
|
|
rel->distribute_keys = NIL;
|
|
} else {
|
|
ListCell* lc = NULL;
|
|
ListCell* lc2 = NULL;
|
|
foreach (lc, distribute_index) {
|
|
int resno = lfirst_int(lc);
|
|
Var* relvar = NULL;
|
|
|
|
if (rel->base_rel != NULL) {
|
|
/*
|
|
* for cost-base query rewrite dummy subquery rel, subplan targetlist
|
|
* is in same order as rel targetlist, so find it by sequence
|
|
*/
|
|
Expr* expr = (Expr*)list_nth(rel->reltargetlist, resno - 1);
|
|
relvar = locate_distribute_var(expr);
|
|
AssertEreport(relvar != NULL, MOD_OPT, "");
|
|
rel->distribute_keys = lappend(rel->distribute_keys, relvar);
|
|
} else {
|
|
/*
|
|
* Find from subquery targetlist for distribute key. We should traverse
|
|
* the targetlist and get the real var, because targetlist of subquery can
|
|
* be a subset of subplan's targetlist, and there can be type cast on base
|
|
* vars
|
|
*/
|
|
foreach (lc2, rel->reltargetlist) {
|
|
relvar = locate_distribute_var((Expr*)lfirst(lc2));
|
|
if (relvar != NULL && relvar->varattno == resno)
|
|
break;
|
|
}
|
|
/* Find it, then add it to subquery distribute key, or set it to null */
|
|
if (lc2 != NULL)
|
|
rel->distribute_keys = lappend(rel->distribute_keys, relvar);
|
|
else {
|
|
list_free_ext(rel->distribute_keys);
|
|
rel->distribute_keys = NIL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
rel->locator_type = get_locator_type(subplan);
|
|
}
|
|
} else {
|
|
rel->distribute_keys = NIL;
|
|
rel->locator_type = LOCATOR_TYPE_REPLICATED;
|
|
}
|
|
}
|
|
|
|
pathnode->distribute_keys = rel->distribute_keys;
|
|
pathnode->locator_type = rel->locator_type;
|
|
|
|
/* For subquery scan, read it's node group information from sub-plan directly */
|
|
Distribution* distribution = ng_get_dest_distribution(rel->subplan);
|
|
ng_copy_distribution(&pathnode->distribution, distribution);
|
|
#endif
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_functionscan_path
|
|
* Creates a path corresponding to a sequential scan of a function,
|
|
* returning the pathnode.
|
|
*/
|
|
Path* create_functionscan_path(PlannerInfo* root, RelOptInfo* rel)
|
|
{
|
|
Path* pathnode = makeNode(Path);
|
|
|
|
pathnode->pathtype = T_FunctionScan;
|
|
pathnode->parent = rel;
|
|
pathnode->param_info = NULL; /* never parameterized at present */
|
|
pathnode->pathkeys = NIL; /* for now, assume unordered result */
|
|
|
|
#ifdef STREAMPLAN
|
|
pathnode->distribute_keys = rel->distribute_keys;
|
|
pathnode->locator_type = rel->locator_type;
|
|
|
|
/*
|
|
* For function scan path, it's node group will relate to wheather it's in a correlated sub-plan
|
|
* (1) In a correlated sub-plan, it's node group should as same as "correlated sub-plan node group"
|
|
* (2) In a normal sub-plan, it's node group should be in "compute permission node group"
|
|
*/
|
|
Distribution* distribution = NULL;
|
|
if (root->is_correlated) {
|
|
distribution = ng_get_correlated_subplan_group_distribution();
|
|
} else {
|
|
/* We need an exec on everywhere group */
|
|
distribution = ng_get_max_computable_group_distribution();
|
|
}
|
|
ng_copy_distribution(&pathnode->distribution, distribution);
|
|
#endif
|
|
|
|
cost_functionscan(pathnode, root, rel);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_valuesscan_path
|
|
* Creates a path corresponding to a scan of a VALUES list,
|
|
* returning the pathnode.
|
|
*/
|
|
Path* create_valuesscan_path(PlannerInfo* root, RelOptInfo* rel)
|
|
{
|
|
Path* pathnode = makeNode(Path);
|
|
|
|
pathnode->pathtype = T_ValuesScan;
|
|
pathnode->parent = rel;
|
|
pathnode->param_info = NULL; /* never parameterized at present */
|
|
pathnode->pathkeys = NIL; /* result is always unordered */
|
|
|
|
#ifdef STREAMPLAN
|
|
pathnode->distribute_keys = NIL;
|
|
pathnode->locator_type = LOCATOR_TYPE_REPLICATED;
|
|
|
|
/*
|
|
* For values scan path, it's node group will relate to wheather it's in a correlated sub-plan
|
|
* (1) In a correlated sub-plan, it's node group should as same as "correlated sub-plan node group"
|
|
* (2) In a normal sub-plan, it's node group should be in "compute permission node group"
|
|
*/
|
|
Distribution* distribution = NULL;
|
|
if (root->is_correlated) {
|
|
distribution = ng_get_correlated_subplan_group_distribution();
|
|
} else {
|
|
/* We need an exec on everywhere group */
|
|
distribution = ng_get_max_computable_group_distribution();
|
|
}
|
|
ng_copy_distribution(&pathnode->distribution, distribution);
|
|
#endif
|
|
|
|
cost_valuesscan(pathnode, root, rel);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_ctescan_path
|
|
* Creates a path corresponding to a scan of a non-self-reference CTE,
|
|
* returning the pathnode.
|
|
*/
|
|
Path* create_ctescan_path(PlannerInfo* root, RelOptInfo* rel)
|
|
{
|
|
Path* pathnode = makeNode(Path);
|
|
|
|
pathnode->pathtype = T_CteScan;
|
|
pathnode->parent = rel;
|
|
pathnode->param_info = NULL; /* never parameterized at present */
|
|
pathnode->pathkeys = NIL; /* XXX for now, result is always unordered */
|
|
|
|
#ifdef STREAMPLAN
|
|
pathnode->distribute_keys = rel->distribute_keys;
|
|
pathnode->locator_type = rel->locator_type;
|
|
|
|
/* add location information for cte scan path */
|
|
Distribution* distribution = ng_get_default_computing_group_distribution();
|
|
ng_copy_distribution(&pathnode->distribution, distribution);
|
|
#endif
|
|
|
|
cost_ctescan(pathnode, root, rel);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_worktablescan_path
|
|
* Creates a path corresponding to a scan of a self-reference CTE,
|
|
* returning the pathnode.
|
|
*/
|
|
Path* create_worktablescan_path(PlannerInfo* root, RelOptInfo* rel)
|
|
{
|
|
Path* pathnode = makeNode(Path);
|
|
|
|
pathnode->pathtype = T_WorkTableScan;
|
|
pathnode->parent = rel;
|
|
pathnode->param_info = NULL; /* never parameterized at present */
|
|
pathnode->pathkeys = NIL; /* result is always unordered */
|
|
|
|
#ifdef STREAMPLAN
|
|
/* build worktable's distribution info */
|
|
if (IS_STREAM_PLAN && u_sess->attr.attr_sql.enable_stream_recursive) {
|
|
Plan* none_recursive_plan = NULL;
|
|
PlannerInfo* cur_root = root;
|
|
|
|
/* Iteratively find the corresponding root to fetch the non-recursive plan */
|
|
while (cur_root != NULL) {
|
|
if (cur_root->hasRecursion) {
|
|
none_recursive_plan = cur_root->non_recursive_plan;
|
|
break;
|
|
}
|
|
|
|
cur_root = cur_root->parent_root;
|
|
}
|
|
|
|
if (none_recursive_plan == NULL) {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("none_recursive_plan could not be NULL")));
|
|
}
|
|
/*
|
|
* For worktablescan path, we inherit the plan distribution information from the
|
|
* none-recursive term.
|
|
*/
|
|
pathnode->distribute_keys = NIL;
|
|
pathnode->locator_type = none_recursive_plan->exec_nodes->baselocatortype;
|
|
|
|
/* add location information for cte scan path */
|
|
Distribution* distribution = ng_get_default_computing_group_distribution();
|
|
ng_copy_distribution(&pathnode->distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
/* Cost is the same as for a regular CTE scan */
|
|
cost_ctescan(pathnode, root, rel);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_foreignscan_path
|
|
* Creates a path corresponding to a scan of a foreign table,
|
|
* returning the pathnode.
|
|
*
|
|
* This function is never called from core Postgres; rather, it's expected
|
|
* to be called by the GetForeignPaths function of a foreign data wrapper.
|
|
* We make the FDW supply all fields of the path, since we do not have any
|
|
* way to calculate them in core.
|
|
*/
|
|
ForeignPath* create_foreignscan_path(PlannerInfo* root, RelOptInfo* rel, Cost startup_cost, Cost total_cost,
|
|
List* pathkeys, Relids required_outer, List* fdw_private, int dop)
|
|
{
|
|
ForeignPath* pathnode = makeNode(ForeignPath);
|
|
|
|
pathnode->path.pathtype = T_ForeignScan;
|
|
pathnode->path.parent = rel;
|
|
pathnode->path.param_info = get_baserel_parampathinfo(root, rel, required_outer);
|
|
set_path_rows(&pathnode->path, rel->rows, rel->multiple);
|
|
pathnode->path.startup_cost = startup_cost;
|
|
pathnode->path.total_cost = total_cost;
|
|
pathnode->path.pathkeys = pathkeys;
|
|
pathnode->path.locator_type = rel->locator_type;
|
|
pathnode->path.stream_cost = 0;
|
|
|
|
pathnode->fdw_private = fdw_private;
|
|
|
|
pathnode->path.dop = 1;
|
|
dop = SET_DOP(dop);
|
|
|
|
/* Create a parallel foreignscan path. */
|
|
if (root->parse && dop > 1) {
|
|
RangeTblEntry* source = rt_fetch(rel->relid, root->parse->rtable);
|
|
|
|
AssertEreport(NULL != source, MOD_OPT_JOIN, "There should be rtable in table list");
|
|
|
|
Oid tblId = source->relid;
|
|
|
|
ServerTypeOption serverType = getServerType(tblId);
|
|
|
|
/*
|
|
* This function is called by each kind of FDW_handler's xxxForeignGetPaths, we should
|
|
* judge which pg_foreign_server used in the query. Now we support SMP for server with
|
|
* different scope.
|
|
* OBS Server: we support OBS roundrobin table SMP feature for command
|
|
* CMD_INSERT && CMD_SELECT
|
|
* CMD_INSERT: insert into table select * from OBS_TBL;
|
|
* CMD_SELECT: select xxx from OBS_TBL, table,xxx where xxx;
|
|
* we support two kinds of OBS table, roundrobin and replicate. If we scan
|
|
* roundrobin table, the execute plan always looks like
|
|
* streaming(Gather) or streaming(redistribute)
|
|
* foreign scan: obs table
|
|
* It is comfortable to add smp foreign scan for this scenario.
|
|
* HDFS Server: we don't add smp feature for this kind of server. No reason.
|
|
* Others: Keep constant with the original logic.
|
|
*/
|
|
if (T_OBS_SERVER == serverType) {
|
|
if ((CMD_SELECT == root->parse->commandType || CMD_INSERT == root->parse->commandType) &&
|
|
LOCATOR_TYPE_RROBIN == source->locator_type)
|
|
pathnode->path.dop = u_sess->opt_cxt.query_dop;
|
|
} else if (T_HDFS_SERVER == serverType) {
|
|
if ((CMD_SELECT == root->parse->commandType || CMD_INSERT == root->parse->commandType) &&
|
|
LOCATOR_TYPE_RROBIN == source->locator_type)
|
|
pathnode->path.dop = u_sess->opt_cxt.query_dop;
|
|
} else if (T_PGFDW_SERVER == serverType) {
|
|
if ((CMD_SELECT == root->parse->commandType || CMD_INSERT == root->parse->commandType) &&
|
|
LOCATOR_TYPE_RROBIN == source->locator_type)
|
|
pathnode->path.dop = u_sess->opt_cxt.query_dop;
|
|
} else {
|
|
/*
|
|
* Parallelize foreign scan.
|
|
* When 'INSERT INTO .. SELECT * FROM foreign_table'.
|
|
* The destination table is hashed rather than replicate,
|
|
* and the source table must be gds foreign table.
|
|
*/
|
|
if (CMD_INSERT == root->parse->commandType) {
|
|
/* Check if it is obs source, OBS text and csv are not supported. */
|
|
DistImportPlanState* planstate = (DistImportPlanState*)rel->fdw_private;
|
|
const char* first_url = strVal(lfirst(list_head(planstate->source)));
|
|
|
|
/*
|
|
* Only support destination table of hash distribution,
|
|
* and normal mode of gds import.
|
|
*/
|
|
if (!is_obs_protocol(first_url) && MODE_NORMAL == planstate->mode) {
|
|
pathnode->path.dop = u_sess->opt_cxt.query_dop;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add location information for foreign scan path.
|
|
* It should be in installation group.
|
|
*/
|
|
Distribution* distribution = ng_get_installation_group_distribution();
|
|
ng_copy_distribution(&pathnode->path.distribution, distribution);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* calc_nestloop_required_outer
|
|
* Compute the required_outer set for a nestloop join path
|
|
*
|
|
* Note: result must not share storage with either input
|
|
*/
|
|
Relids calc_nestloop_required_outer(Path* outer_path, Path* inner_path)
|
|
{
|
|
Relids outer_paramrels = PATH_REQ_OUTER(outer_path);
|
|
Relids inner_paramrels = PATH_REQ_OUTER(inner_path);
|
|
Relids required_outer;
|
|
|
|
/* inner_path can require rels from outer path, but not vice versa */
|
|
AssertEreport(!bms_overlap(outer_paramrels, inner_path->parent->relids),
|
|
MOD_OPT_JOIN,
|
|
"Outer path shouldn't require rels from inner path");
|
|
/* easy case if inner path is not parameterized */
|
|
if (!inner_paramrels)
|
|
return bms_copy(outer_paramrels);
|
|
/* else, form the union ... */
|
|
required_outer = bms_union(outer_paramrels, inner_paramrels);
|
|
/* ... and remove any mention of now-satisfied outer rels */
|
|
required_outer = bms_del_members(required_outer, outer_path->parent->relids);
|
|
/* maintain invariant that required_outer is exactly NULL if empty */
|
|
if (bms_is_empty(required_outer)) {
|
|
bms_free_ext(required_outer);
|
|
required_outer = NULL;
|
|
}
|
|
return required_outer;
|
|
}
|
|
|
|
/*
|
|
* calc_non_nestloop_required_outer
|
|
* Compute the required_outer set for a merge or hash join path
|
|
*
|
|
* Note: result must not share storage with either input
|
|
*/
|
|
Relids calc_non_nestloop_required_outer(Path* outer_path, Path* inner_path)
|
|
{
|
|
Relids outer_paramrels = PATH_REQ_OUTER(outer_path);
|
|
Relids inner_paramrels = PATH_REQ_OUTER(inner_path);
|
|
Relids required_outer;
|
|
|
|
/* neither path can require rels from the other */
|
|
AssertEreport(!bms_overlap(outer_paramrels, inner_path->parent->relids),
|
|
MOD_OPT_JOIN,
|
|
"Outer path shouldn't require rels from inner path");
|
|
AssertEreport(!bms_overlap(inner_paramrels, outer_path->parent->relids),
|
|
MOD_OPT_JOIN,
|
|
"Inner path shouldn't require rels from outer path");
|
|
/* form the union ... */
|
|
required_outer = bms_union(outer_paramrels, inner_paramrels);
|
|
/* we do not need an explicit test for empty; bms_union gets it right */
|
|
return required_outer;
|
|
}
|
|
|
|
/*
|
|
* Target : Print relids in pg_log when log_min_messages <= DEBUG3.
|
|
* In : The first relids and second relids to print, root contains rels' name.
|
|
* Out : The buf contains the print string.
|
|
* Return : NA
|
|
*/
|
|
void debug3_print_two_relids(Relids first_relids, Relids second_relids, PlannerInfo* root, StringInfoData* buf)
|
|
{
|
|
initStringInfo(buf);
|
|
|
|
if (root != NULL && root->parse != NULL) {
|
|
appendStringInfoString(buf, debug1_print_relids(first_relids, root->parse->rtable));
|
|
appendStringInfoString(buf, " || ");
|
|
appendStringInfoString(buf, debug1_print_relids(second_relids, root->parse->rtable));
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Target : Find whether there exists indirect equivalence relationship between inner_relids and outer_relids.
|
|
* In : The inner relids and outer relids to scan, root contains rels' equivalence classes.
|
|
* Out : NA. * Return : Return true if exists indirect equivalence relationship, otherwise return false.
|
|
* Notes : If find a indirect path to hashjoin or mergejoin the rels, we can add g_instance.cost_cxt.disable_cost to
|
|
* nestloop path.
|
|
*/
|
|
bool equivalence_class_overlap(PlannerInfo* root, Relids outer_relids, Relids inner_relids)
|
|
{
|
|
if (root->eq_classes == NULL)
|
|
return false;
|
|
|
|
StringInfoData buf;
|
|
bool still_has_eq_class_to_match = true;
|
|
|
|
/* The expanded equivalence classes based on inner relids */
|
|
Relids expanded_eq_classes_of_inner_relids = bms_copy(inner_relids);
|
|
|
|
/* The mark list of eq classes tells which eq class has already been linked to inner rels */
|
|
bool* mark_list_of_linked_eq_class = (bool*)palloc0((root->eq_classes->length) * sizeof(bool));
|
|
|
|
/* print Outer relids and Inner relids for debug */
|
|
if (log_min_messages <= DEBUG3 && root->parse) {
|
|
initStringInfo(&buf);
|
|
char* relid_string = debug1_print_relids(outer_relids, root->parse->rtable);
|
|
appendStringInfoString(&buf, relid_string);
|
|
pfree_ext(relid_string);
|
|
ereport(DEBUG3, (errmodule(MOD_OPT_JOIN), (errmsg("[EQ] Outer relids:\n\n%s\n", buf.data), errhidestmt(true))));
|
|
pfree_ext(buf.data);
|
|
initStringInfo(&buf);
|
|
relid_string = debug1_print_relids(inner_relids, root->parse->rtable);
|
|
appendStringInfoString(&buf, relid_string);
|
|
pfree_ext(relid_string);
|
|
ereport(DEBUG3, (errmodule(MOD_OPT_JOIN), (errmsg("[EQ] Inner relids:\n\n%s\n", buf.data), errhidestmt(true))));
|
|
pfree_ext(buf.data);
|
|
}
|
|
|
|
/* Scan the eq classes list again and again until no new eq class can be linked to inner rels in one loop */
|
|
while (still_has_eq_class_to_match) {
|
|
int count = 0;
|
|
ListCell* lc = NULL;
|
|
still_has_eq_class_to_match = false;
|
|
|
|
foreach (lc, root->eq_classes) {
|
|
EquivalenceClass* eqc = (EquivalenceClass*)lfirst(lc);
|
|
|
|
/*
|
|
* If the equivalence relationship can finally reach a const,
|
|
* then it can be replaced by a filter.
|
|
* Else there can be hashjoin or mergejoin path even though not directly.
|
|
*/
|
|
if (!eqc->ec_has_const && !mark_list_of_linked_eq_class[count]) {
|
|
ListCell* slc = NULL;
|
|
bool inner_in_eq = false;
|
|
Bitmapset* linked_relids = (Relids)palloc0(sizeof(Bitmapset));
|
|
BMS_Comparison bms_result;
|
|
|
|
foreach (slc, eqc->ec_members) {
|
|
EquivalenceMember* em = (EquivalenceMember*)lfirst(slc);
|
|
linked_relids = bms_add_members(linked_relids, em->em_relids);
|
|
bms_result = bms_subset_compare(em->em_relids, expanded_eq_classes_of_inner_relids);
|
|
if (bms_result == BMS_EQUAL || bms_result == BMS_SUBSET1) {
|
|
inner_in_eq = true;
|
|
mark_list_of_linked_eq_class[count] = true;
|
|
still_has_eq_class_to_match = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If find equivalence relationship, expand the eq_classes linked to innner_relids
|
|
* and compare with outer_relids.
|
|
*/
|
|
bms_result = bms_subset_compare(expanded_eq_classes_of_inner_relids, linked_relids);
|
|
|
|
if (inner_in_eq && bms_result != BMS_EQUAL && bms_result != BMS_SUBSET2) {
|
|
if (log_min_messages <= DEBUG3) {
|
|
debug3_print_two_relids(expanded_eq_classes_of_inner_relids, linked_relids, root, &buf);
|
|
ereport(DEBUG3,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
(errmsg("[EQ] Expand relids eq-linked (%d) to inner relids (%d):\n\n%s\n",
|
|
expanded_eq_classes_of_inner_relids->nwords,
|
|
linked_relids->nwords,
|
|
buf.data),
|
|
errhidestmt(true))));
|
|
pfree_ext(buf.data);
|
|
}
|
|
expanded_eq_classes_of_inner_relids =
|
|
bms_add_members(expanded_eq_classes_of_inner_relids, linked_relids);
|
|
if (bms_overlap(expanded_eq_classes_of_inner_relids, outer_relids)) {
|
|
if (log_min_messages <= DEBUG3) {
|
|
debug3_print_two_relids(outer_relids, expanded_eq_classes_of_inner_relids, root, &buf);
|
|
ereport(DEBUG3,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
(errmsg("[EQ] Find outer_relids in eq-expanded inner relids:\n\n%s\n", buf.data),
|
|
errhidestmt(true))));
|
|
pfree_ext(buf.data);
|
|
}
|
|
pfree_ext(mark_list_of_linked_eq_class);
|
|
bms_free_ext(linked_relids);
|
|
bms_free_ext(expanded_eq_classes_of_inner_relids);
|
|
return true;
|
|
}
|
|
} else {
|
|
if (log_min_messages <= DEBUG3) {
|
|
debug3_print_two_relids(expanded_eq_classes_of_inner_relids, linked_relids, root, &buf);
|
|
ereport(DEBUG3,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
(errmsg("[EQ] Not find any member eq-linked to inner_relids in eq-classes:\n\n%s\n",
|
|
buf.data),
|
|
errhidestmt(true))));
|
|
pfree_ext(buf.data);
|
|
}
|
|
}
|
|
bms_free_ext(linked_relids);
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (log_min_messages <= DEBUG3) {
|
|
debug3_print_two_relids(outer_relids, expanded_eq_classes_of_inner_relids, root, &buf);
|
|
ereport(DEBUG3,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
(errmsg("[EQ] Not find outer_relids in eq-expanded inner relids:\n\n%s\n", buf.data),
|
|
errhidestmt(true))));
|
|
pfree_ext(buf.data);
|
|
}
|
|
|
|
pfree_ext(mark_list_of_linked_eq_class);
|
|
bms_free_ext(expanded_eq_classes_of_inner_relids);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* create_nestloop_path
|
|
* Creates a pathnode corresponding to a nestloop join between two
|
|
* relations.
|
|
*
|
|
* 'joinrel' is the join relation.
|
|
* 'jointype' is the type of join required
|
|
* 'workspace' is the result from initial_cost_nestloop
|
|
* 'sjinfo' is extra info about the join for selectivity estimation
|
|
* 'semifactors' contains valid data if jointype is SEMI or ANTI
|
|
* 'outer_path' is the outer path
|
|
* 'inner_path' is the inner path
|
|
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
|
|
* 'pathkeys' are the path keys of the new join path
|
|
* 'required_outer' is the set of required outer rels
|
|
*
|
|
* Returns the resulting path node.
|
|
*/
|
|
NestPath* create_nestloop_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype, JoinCostWorkspace* workspace,
|
|
SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors, Path* outer_path, Path* inner_path,
|
|
List* restrict_clauses, List* pathkeys, Relids required_outer, int dop)
|
|
{
|
|
NestPath* pathnode = makeNode(NestPath);
|
|
Relids inner_req_outer = PATH_REQ_OUTER(inner_path);
|
|
bool try_eq_related_indirectly = false;
|
|
bool hasalternative = check_join_method_alternative(
|
|
restrict_clauses, outer_path->parent, inner_path->parent, jointype, &try_eq_related_indirectly);
|
|
|
|
if (outer_path->parent != NULL && inner_path->parent != NULL && root != NULL && !hasalternative &&
|
|
try_eq_related_indirectly && !u_sess->attr.attr_sql.enable_nestloop)
|
|
hasalternative = equivalence_class_overlap(root, outer_path->parent->relids, inner_path->parent->relids);
|
|
if (!hasalternative && log_min_messages <= DEBUG3) {
|
|
StringInfoData buf;
|
|
|
|
if (outer_path->parent == NULL) {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("outer_path and parent in outer_path could not be NULL")));
|
|
}
|
|
if (inner_path->parent == NULL) {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("inner_path and parent in inner_path could not be NULL")));
|
|
}
|
|
|
|
debug3_print_two_relids(outer_path->parent->relids, inner_path->parent->relids, root, &buf);
|
|
ereport(
|
|
DEBUG3, (errmodule(MOD_OPT_JOIN), "[OPTHashjoin]Print Outer relids and Inner relids:\n\n%s\n", buf.data));
|
|
pfree_ext(buf.data);
|
|
|
|
ListCell* l = NULL;
|
|
foreach (l, restrict_clauses) {
|
|
RestrictInfo* restrictinfo = (RestrictInfo*)lfirst(l);
|
|
StringInfoData buf2;
|
|
debug3_print_two_relids(restrictinfo->left_relids, restrictinfo->right_relids, root, &buf2);
|
|
ereport(
|
|
DEBUG3, (errmodule(MOD_OPT_JOIN), "[OPTHashjoin]Print clause left and right side:\n\n%s\n", buf2.data));
|
|
pfree_ext(buf2.data);
|
|
}
|
|
}
|
|
/*
|
|
* If the inner path is parameterized by the outer, we must drop any
|
|
* restrict_clauses that are due to be moved into the inner path. We have
|
|
* to do this now, rather than postpone the work till createplan time,
|
|
* because the restrict_clauses list can affect the size and cost
|
|
* estimates for this path.
|
|
*/
|
|
if (outer_path->parent == NULL) {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("outer_path and parent in outer_path could not be NULL")));
|
|
}
|
|
|
|
if (bms_overlap(inner_req_outer, outer_path->parent->relids)) {
|
|
if (inner_path->parent == NULL) {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("inner_path and parent in inner_path could not be NULL")));
|
|
}
|
|
|
|
Relids inner_and_outer = bms_union(inner_path->parent->relids, inner_req_outer);
|
|
List* jclauses = NIL;
|
|
ListCell* lc = NULL;
|
|
|
|
foreach (lc, restrict_clauses) {
|
|
RestrictInfo* rinfo = (RestrictInfo*)lfirst(lc);
|
|
|
|
if (!join_clause_is_movable_into(rinfo, inner_path->parent->relids, inner_and_outer))
|
|
jclauses = lappend(jclauses, rinfo);
|
|
}
|
|
restrict_clauses = jclauses;
|
|
}
|
|
|
|
pathnode->path.pathtype = T_NestLoop;
|
|
pathnode->path.parent = joinrel;
|
|
pathnode->path.param_info =
|
|
get_joinrel_parampathinfo(root, joinrel, outer_path, inner_path, sjinfo, required_outer, &restrict_clauses);
|
|
pathnode->path.pathkeys = pathkeys;
|
|
if (IsA(outer_path, StreamPath) && NIL == outer_path->pathkeys) {
|
|
pathnode->path.pathkeys = NIL;
|
|
}
|
|
pathnode->path.dop = dop;
|
|
pathnode->jointype = jointype;
|
|
pathnode->outerjoinpath = outer_path;
|
|
pathnode->innerjoinpath = inner_path;
|
|
pathnode->joinrestrictinfo = restrict_clauses;
|
|
|
|
#ifdef STREAMPLAN
|
|
pathnode->path.locator_type = locator_type_join(outer_path->locator_type, inner_path->locator_type);
|
|
|
|
if (IS_STREAM_PLAN) {
|
|
/* add location information for nest loop join path */
|
|
Distribution* distribution = ng_get_join_distribution(outer_path, inner_path);
|
|
ng_copy_distribution(&pathnode->path.distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
final_cost_nestloop(root, pathnode, workspace, sjinfo, semifactors, hasalternative, dop);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_mergejoin_path
|
|
* Creates a pathnode corresponding to a mergejoin join between
|
|
* two relations
|
|
*
|
|
* 'joinrel' is the join relation
|
|
* 'jointype' is the type of join required
|
|
* 'workspace' is the result from initial_cost_mergejoin
|
|
* 'sjinfo' is extra info about the join for selectivity estimation
|
|
* 'outer_path' is the outer path
|
|
* 'inner_path' is the inner path
|
|
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
|
|
* 'pathkeys' are the path keys of the new join path
|
|
* 'required_outer' is the set of required outer rels
|
|
* 'mergeclauses' are the RestrictInfo nodes to use as merge clauses
|
|
* (this should be a subset of the restrict_clauses list)
|
|
* 'outersortkeys' are the sort varkeys for the outer relation
|
|
* 'innersortkeys' are the sort varkeys for the inner relation
|
|
*/
|
|
MergePath* create_mergejoin_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype,
|
|
JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, Path* outer_path, Path* inner_path, List* restrict_clauses,
|
|
List* pathkeys, Relids required_outer, List* mergeclauses, List* outersortkeys, List* innersortkeys)
|
|
{
|
|
MergePath* pathnode = makeNode(MergePath);
|
|
bool try_eq_related_indirectly = false;
|
|
|
|
pathnode->jpath.path.pathtype = T_MergeJoin;
|
|
pathnode->jpath.path.parent = joinrel;
|
|
pathnode->jpath.path.param_info =
|
|
get_joinrel_parampathinfo(root, joinrel, outer_path, inner_path, sjinfo, required_outer, &restrict_clauses);
|
|
pathnode->jpath.path.pathkeys = pathkeys;
|
|
pathnode->jpath.jointype = jointype;
|
|
pathnode->jpath.outerjoinpath = outer_path;
|
|
pathnode->jpath.innerjoinpath = inner_path;
|
|
pathnode->jpath.joinrestrictinfo = restrict_clauses;
|
|
pathnode->path_mergeclauses = mergeclauses;
|
|
pathnode->outersortkeys = outersortkeys;
|
|
pathnode->innersortkeys = innersortkeys;
|
|
/* pathnode->materialize_inner will be set by final_cost_mergejoin */
|
|
#ifdef STREAMPLAN
|
|
pathnode->jpath.path.locator_type = locator_type_join(outer_path->locator_type, inner_path->locator_type);
|
|
|
|
if (IS_STREAM_PLAN) {
|
|
/* add location information for merge join path */
|
|
Distribution* distribution = ng_get_join_distribution(outer_path, inner_path);
|
|
ng_copy_distribution(&pathnode->jpath.path.distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
final_cost_mergejoin(root,
|
|
pathnode,
|
|
workspace,
|
|
sjinfo,
|
|
check_join_method_alternative(
|
|
restrict_clauses, outer_path->parent, inner_path->parent, jointype, &try_eq_related_indirectly));
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* create_hashjoin_path
|
|
* Creates a pathnode corresponding to a hash join between two relations.
|
|
*
|
|
* 'joinrel' is the join relation
|
|
* 'jointype' is the type of join required
|
|
* 'workspace' is the result from initial_cost_hashjoin
|
|
* 'sjinfo' is extra info about the join for selectivity estimation
|
|
* 'semifactors' contains valid data if jointype is SEMI or ANTI
|
|
* 'outer_path' is the cheapest outer path
|
|
* 'inner_path' is the cheapest inner path
|
|
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
|
|
* 'required_outer' is the set of required outer rels
|
|
* 'hashclauses' are the RestrictInfo nodes to use as hash clauses
|
|
* (this should be a subset of the restrict_clauses list)
|
|
*/
|
|
HashPath* create_hashjoin_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype, JoinCostWorkspace* workspace,
|
|
SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors, Path* outer_path, Path* inner_path,
|
|
List* restrict_clauses, Relids required_outer, List* hashclauses, int dop)
|
|
{
|
|
HashPath* pathnode = makeNode(HashPath);
|
|
bool try_eq_related_indirectly = false;
|
|
|
|
pathnode->jpath.path.pathtype = T_HashJoin;
|
|
pathnode->jpath.path.parent = joinrel;
|
|
pathnode->jpath.path.param_info =
|
|
get_joinrel_parampathinfo(root, joinrel, outer_path, inner_path, sjinfo, required_outer, &restrict_clauses);
|
|
|
|
/*
|
|
* A hashjoin never has pathkeys, since its output ordering is
|
|
* unpredictable due to possible batching. XXX If the inner relation is
|
|
* small enough, we could instruct the executor that it must not batch,
|
|
* and then we could assume that the output inherits the outer relation's
|
|
* ordering, which might save a sort step. However there is considerable
|
|
* downside if our estimate of the inner relation size is badly off. For
|
|
* the moment we don't risk it. (Note also that if we wanted to take this
|
|
* seriously, joinpath.c would have to consider many more paths for the
|
|
* outer rel than it does now.)
|
|
*/
|
|
pathnode->jpath.path.pathkeys = NIL;
|
|
pathnode->jpath.path.dop = dop;
|
|
pathnode->jpath.jointype = jointype;
|
|
pathnode->jpath.outerjoinpath = outer_path;
|
|
pathnode->jpath.innerjoinpath = inner_path;
|
|
pathnode->jpath.joinrestrictinfo = restrict_clauses;
|
|
pathnode->path_hashclauses = hashclauses;
|
|
|
|
#ifdef STREAMPLAN
|
|
pathnode->jpath.path.locator_type = locator_type_join(inner_path->locator_type, outer_path->locator_type);
|
|
|
|
if (IS_STREAM_PLAN) {
|
|
/* add location information for hash join path */
|
|
Distribution* distribution = ng_get_join_distribution(outer_path, inner_path);
|
|
ng_copy_distribution(&pathnode->jpath.path.distribution, distribution);
|
|
}
|
|
#endif
|
|
|
|
/* final_cost_hashjoin will fill in pathnode->num_batches */
|
|
final_cost_hashjoin(root,
|
|
pathnode,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
check_join_method_alternative(
|
|
restrict_clauses, outer_path->parent, inner_path->parent, jointype, &try_eq_related_indirectly),
|
|
dop);
|
|
|
|
return pathnode;
|
|
}
|
|
|
|
/*
|
|
* reparameterize_path
|
|
* Attempt to modify a Path to have greater parameterization
|
|
*
|
|
* We use this to attempt to bring all child paths of an appendrel to the
|
|
* same parameterization level, ensuring that they all enforce the same set
|
|
* of join quals (and thus that that parameterization can be attributed to
|
|
* an append path built from such paths). Currently, only a few path types
|
|
* are supported here, though more could be added at need. We return NULL
|
|
* if we can't reparameterize the given path.
|
|
*
|
|
* Note: we intentionally do not pass created paths to add_path(); it would
|
|
* possibly try to delete them on the grounds of being cost-inferior to the
|
|
* paths they were made from, and we don't want that. Paths made here are
|
|
* not necessarily of general-purpose usefulness, but they can be useful
|
|
* as members of an append path.
|
|
*/
|
|
Path* reparameterize_path(PlannerInfo* root, Path* path, Relids required_outer, double loop_count)
|
|
{
|
|
RelOptInfo* rel = path->parent;
|
|
|
|
/* Can only increase, not decrease, path's parameterization */
|
|
if (!bms_is_subset(PATH_REQ_OUTER(path), required_outer))
|
|
return NULL;
|
|
switch (path->pathtype) {
|
|
case T_SeqScan:
|
|
return create_seqscan_path(root, rel, required_outer);
|
|
case T_IndexScan:
|
|
case T_IndexOnlyScan: {
|
|
IndexPath* ipath = (IndexPath*)path;
|
|
IndexPath* newpath = makeNode(IndexPath);
|
|
|
|
/*
|
|
* We can't use create_index_path directly, and would not want
|
|
* to because it would re-compute the indexqual conditions
|
|
* which is wasted effort. Instead we hack things a bit:
|
|
* flat-copy the path node, revise its param_info, and redo
|
|
* the cost estimate.
|
|
*/
|
|
errno_t errorno = EOK;
|
|
errorno = memcpy_s(newpath, sizeof(IndexPath), ipath, sizeof(IndexPath));
|
|
securec_check(errorno, "", "");
|
|
|
|
newpath->path.param_info = get_baserel_parampathinfo(root, rel, required_outer);
|
|
cost_index(newpath, root, loop_count);
|
|
return (Path*)newpath;
|
|
}
|
|
case T_BitmapHeapScan: {
|
|
BitmapHeapPath* bpath = (BitmapHeapPath*)path;
|
|
|
|
return (Path*)create_bitmap_heap_path(root, rel, bpath->bitmapqual, required_outer, loop_count);
|
|
}
|
|
case T_SubqueryScan:
|
|
return create_subqueryscan_path(root, rel, path->pathkeys, required_outer);
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* check_join_method_alternative
|
|
*
|
|
* check if there's any alternatives when we disable one or more methods, and if
|
|
* not, we should add large cost for the sole path, which will influence judgement
|
|
* of other joins
|
|
*/
|
|
bool check_join_method_alternative(
|
|
List* restrictlist, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinType jointype, bool* try_eq_related_indirectly)
|
|
{
|
|
bool hasalternative = false;
|
|
ListCell* l = NULL;
|
|
|
|
foreach (l, restrictlist) {
|
|
RestrictInfo* restrictinfo = (RestrictInfo*)lfirst(l);
|
|
|
|
/* Check if clause is a hashable or mergeable operator clause */
|
|
if (restrictinfo->can_join && clause_sides_match_join(restrictinfo, outerrel, innerrel)) {
|
|
if (u_sess->attr.attr_sql.enable_hashjoin && restrictinfo->hashjoinoperator != InvalidOid)
|
|
hasalternative = true;
|
|
if (u_sess->attr.attr_sql.enable_mergejoin && restrictinfo->mergeopfamilies != NIL)
|
|
hasalternative = true;
|
|
if (u_sess->attr.attr_sql.enable_hashjoin || u_sess->attr.attr_sql.enable_mergejoin)
|
|
*try_eq_related_indirectly = true;
|
|
}
|
|
if (hasalternative)
|
|
break;
|
|
}
|
|
if (u_sess->attr.attr_sql.enable_nestloop && jointype != JOIN_FULL)
|
|
hasalternative = true;
|
|
|
|
return hasalternative;
|
|
}
|
|
|
|
#ifdef STREAMPLAN
|
|
|
|
bool is_replicated_path(Path* path)
|
|
{
|
|
return path->locator_type == LOCATOR_TYPE_REPLICATED;
|
|
}
|
|
|
|
/*
|
|
* @Description: Find the distribute location in path's targetlist.
|
|
* @in distribute_keys: distribute key list.
|
|
* @in target_list: target list.
|
|
* @return List: locations in targetlist.
|
|
*/
|
|
static List* find_distrikey_in_targetlist(List* distribute_keys, List* target_list)
|
|
{
|
|
List* dis_location = NIL;
|
|
ListCell* lc1 = NULL;
|
|
ListCell* lc2 = NULL;
|
|
|
|
if (NIL == target_list || NIL == distribute_keys)
|
|
return NIL;
|
|
|
|
foreach (lc1, distribute_keys) {
|
|
Var* var1 = (Var*)lfirst(lc1);
|
|
if (IsA(var1, Var)) {
|
|
AttrNumber varloc = 1;
|
|
/*
|
|
* Check if the distribute_key in the targetlist,
|
|
* if so, record the location in targetlist.
|
|
*/
|
|
foreach (lc2, target_list) {
|
|
Var* var2 = (Var*)lfirst(lc2);
|
|
if (IsA(var2, Var) && var1->varattno == var2->varattno) {
|
|
dis_location = lappend_int(dis_location, varloc);
|
|
break;
|
|
}
|
|
varloc++;
|
|
}
|
|
} else {
|
|
list_free_ext(dis_location);
|
|
dis_location = NIL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we can find all distribute keys in targetlists,
|
|
* then we can not use this dis_location as append distribute key.
|
|
*/
|
|
if (list_length(dis_location) < list_length(distribute_keys))
|
|
return NIL;
|
|
|
|
return dis_location;
|
|
}
|
|
|
|
/*
|
|
* @Description: Mark append node's distribute_keys and locator_type information.
|
|
* @in root: Per-query information for planning/optimization.
|
|
* @in rel: Append relation information.
|
|
* @in pathnode: Append node.
|
|
* @in subpaths: Append sub path.
|
|
*/
|
|
static void mark_append_path(PlannerInfo* root, RelOptInfo* rel, Path* pathnode, List* subpaths)
|
|
{
|
|
Path* subpath = NULL;
|
|
ListCell* cell = NULL;
|
|
Bitmapset* replicatePathSet = NULL;
|
|
int subPathIndex = 0;
|
|
List* dis_varattno = NIL;
|
|
List* dis_varattno2 = NIL;
|
|
Distribution* target_distribution = NULL;
|
|
|
|
foreach (cell, subpaths) {
|
|
subpath = (Path*)lfirst(cell);
|
|
|
|
target_distribution = (NULL == target_distribution) ? ng_get_dest_distribution(subpath) : target_distribution;
|
|
|
|
if (root != NULL && root->is_correlated) {
|
|
Distribution* distribution = ng_get_dest_distribution(subpath);
|
|
Distribution* subplan_dist = ng_get_correlated_subplan_group_distribution();
|
|
|
|
if (!is_replicated_path(subpath) || !ng_is_same_group(distribution, subplan_dist)) {
|
|
subpath = create_stream_path(root, rel, STREAM_BROADCAST, NIL, NIL, subpath, 1.0, subplan_dist);
|
|
}
|
|
|
|
ContainStreamContext context;
|
|
context.outer_relids = NULL;
|
|
context.only_check_stream = true;
|
|
context.under_materialize_all = false;
|
|
context.has_stream = false;
|
|
context.has_parameterized_path = false;
|
|
context.has_cstore_index_delta = false;
|
|
|
|
stream_path_walker(subpath, &context);
|
|
if (context.has_stream || context.has_cstore_index_delta) {
|
|
Cost rescan_startup_cost, rescan_total_cost;
|
|
|
|
subpath = (Path*)create_material_path(subpath, true);
|
|
cost_rescan(
|
|
root, subpath, &rescan_startup_cost, &rescan_total_cost, &((MaterialPath*)subpath)->mem_info);
|
|
((MaterialPath*)subpath)->mem_info.regressCost *= DEFAULT_NUM_ROWS;
|
|
}
|
|
|
|
if (subpath != lfirst(cell)) {
|
|
lfirst(cell) = subpath;
|
|
}
|
|
}
|
|
/* For append path, we eliminate dummy path */
|
|
if (subpath->parent->subplan != NULL && is_dummy_plan(subpath->parent->subplan)) {
|
|
subPathIndex--;
|
|
} else if (is_replicated_path(subpath)) {
|
|
replicatePathSet = bms_add_member(replicatePathSet, subPathIndex);
|
|
} else if (subpath->distribute_keys != NIL) { /* Check if there is common distribute key for all path */
|
|
if (cell == list_head(subpaths)) { /* first subpath */
|
|
dis_varattno = find_distrikey_in_targetlist(subpath->distribute_keys, subpath->parent->reltargetlist);
|
|
} else { /* other subpath */
|
|
dis_varattno2 = find_distrikey_in_targetlist(subpath->distribute_keys, subpath->parent->reltargetlist);
|
|
|
|
/* The dis_varattno should be exact the same. */
|
|
if (!equal(dis_varattno, dis_varattno2)) {
|
|
dis_varattno = NIL;
|
|
dis_varattno2 = NIL;
|
|
}
|
|
}
|
|
} else {
|
|
dis_varattno = NIL;
|
|
dis_varattno2 = NIL;
|
|
}
|
|
subPathIndex++;
|
|
}
|
|
|
|
if (subPathIndex == bms_num_members(replicatePathSet)) {
|
|
pathnode->distribute_keys = NIL;
|
|
pathnode->locator_type = LOCATOR_TYPE_REPLICATED;
|
|
|
|
rel->locator_type = pathnode->locator_type;
|
|
rel->distribute_keys = pathnode->distribute_keys;
|
|
} else if (bms_is_empty(replicatePathSet) && dis_varattno != NIL) {
|
|
/* If we have common distribute key, marked it locator type hash */
|
|
ListCell* lc = NULL;
|
|
|
|
foreach (cell, dis_varattno) {
|
|
AttrNumber disno = lfirst_int(cell);
|
|
AttrNumber varno = 1;
|
|
foreach (lc, rel->reltargetlist) {
|
|
Var* var = (Var*)lfirst(lc);
|
|
if (IsA(var, Var) && varno == disno) {
|
|
pathnode->distribute_keys = lappend(pathnode->distribute_keys, var);
|
|
break;
|
|
}
|
|
varno++;
|
|
}
|
|
}
|
|
pathnode->locator_type = LOCATOR_TYPE_HASH;
|
|
} else {
|
|
pathnode->distribute_keys = rel->distribute_keys;
|
|
pathnode->locator_type = rel->locator_type;
|
|
}
|
|
|
|
/* add location information for append path */
|
|
if (NIL == subpaths) {
|
|
/* dummy node */
|
|
Distribution* distribution = ng_get_default_computing_group_distribution();
|
|
ng_copy_distribution(&pathnode->distribution, distribution);
|
|
} else {
|
|
ng_copy_distribution(&pathnode->distribution, target_distribution);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* find_ec_memeber_for_var:
|
|
* find the equivalence node of key in one eqclass
|
|
* Parameters:
|
|
* @in ec: equivalence class to find the key
|
|
* @in key: the item whose equivalence node is to be found
|
|
* Return:
|
|
* true if found, or false
|
|
*/
|
|
bool find_ec_memeber_for_var(EquivalenceClass* ec, Node* key)
|
|
{
|
|
ListCell* lc = NULL;
|
|
|
|
if (ec == NULL || key == NULL)
|
|
return false;
|
|
|
|
foreach (lc, ec->ec_members) {
|
|
EquivalenceMember* em = (EquivalenceMember*)lfirst(lc);
|
|
Expr* emexpr = NULL;
|
|
|
|
emexpr = em->em_expr;
|
|
|
|
if (IsA(key, Var)) {
|
|
Var* emvar = locate_distribute_var(emexpr);
|
|
if (emvar != NULL && _equalSimpleVar(emvar, key))
|
|
return true;
|
|
} else {
|
|
if (equal(emexpr, key))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* is_ec_usable_for_join:
|
|
* see if the join clause can be found in equivalence class can be used for join
|
|
* and the diskey and joinkey are compatible types for hashing
|
|
*
|
|
* the EquivalenceClass only record the equality types implied from join clauses
|
|
* however, the members in EC might not join directly when have different types of hashing
|
|
* for example if type:date and type:timestamp are in EC but using different hashing functions
|
|
* hence the two cols can not be join directly without redistribution
|
|
*/
|
|
static bool is_ec_usable_for_join(
|
|
Relids suitable_relids, EquivalenceClass* suitable_ec, Node* diskey, Expr* join_clause, bool is_left)
|
|
{
|
|
Node* joinkey = NULL;
|
|
|
|
if (suitable_relids == NULL)
|
|
return false;
|
|
|
|
if (suitable_ec == NULL)
|
|
return false;
|
|
|
|
if (!find_ec_memeber_for_var(suitable_ec, diskey))
|
|
return false;
|
|
|
|
/*
|
|
* if we found a ec member for the diskey, the join_clause
|
|
* should be an OpExpr. but for backward compatiblity with
|
|
* the old original code logic return true if not an OpExpr.
|
|
* however we are expecting an OpExpr here
|
|
*/
|
|
if (!IsA(join_clause, OpExpr))
|
|
return true;
|
|
|
|
/*
|
|
* the join clause is an OpExpr if we reach here
|
|
* extract the joinkey from one side of the join_clause
|
|
* and test it against the diskey for hashing compatiblity
|
|
*/
|
|
joinkey = join_clause_get_join_key((Node*)join_clause, is_left);
|
|
|
|
return is_diskey_and_joinkey_compatible(diskey, joinkey);
|
|
}
|
|
|
|
/*
|
|
* given a join clause as an operator type return the join_key on either side
|
|
*/
|
|
Node* join_clause_get_join_key(Node* join_clause, bool is_var_on_left)
|
|
{
|
|
Node* join_key = NULL;
|
|
OpExpr* join_op = NULL;
|
|
|
|
if (!IsA(join_clause, OpExpr))
|
|
return NULL;
|
|
|
|
join_op = (OpExpr*)join_clause;
|
|
|
|
if (is_var_on_left) {
|
|
join_key = (Node*)linitial(join_op->args);
|
|
} else {
|
|
join_key = (Node*)lsecond(join_op->args);
|
|
}
|
|
|
|
return join_key;
|
|
}
|
|
|
|
/*
|
|
* see if distribute key and join key are compatible for hashing
|
|
*/
|
|
static bool is_diskey_and_joinkey_compatible(Node* diskey, Node* joinkey)
|
|
{
|
|
Oid joinkey_type = InvalidOid;
|
|
Oid diskey_type = InvalidOid;
|
|
|
|
joinkey_type = exprType(joinkey);
|
|
diskey_type = exprType(diskey);
|
|
|
|
if (joinkey_type == InvalidOid || diskey_type == InvalidOid)
|
|
return false;
|
|
|
|
return is_compatible_type(joinkey_type, diskey_type);
|
|
}
|
|
|
|
/*
|
|
* is_distribute_need_on_joinclauses:
|
|
* Judge if redistribution is needed when join with joinclauses based on
|
|
* current distribute key
|
|
* Parameters:
|
|
* @in root: planner info of current query level
|
|
* @in cur_distkeys: distribute key of current rel
|
|
* @in joinclauses: the join clauses used by current rel
|
|
* @in cur_relids: relids of current rel
|
|
* @in other_relids: relids of the other rel joined with current rel
|
|
* @out rrinfo: if redistribution is unnecessary, return restrictinfo on
|
|
* which we can join directly
|
|
* Return:
|
|
* true if redistribution is needed, else false
|
|
*/
|
|
bool is_distribute_need_on_joinclauses(PlannerInfo* root, List* cur_distkeys, List* joinclauses,
|
|
const RelOptInfo* side_rel, const RelOptInfo* other_rel, List** rrinfo)
|
|
{
|
|
ListCell* lcell = NULL;
|
|
Node* diskey = NULL;
|
|
|
|
Relids side_relids = side_rel->relids;
|
|
Relids other_relids = other_rel->relids;
|
|
bool result = false;
|
|
|
|
*rrinfo = NULL;
|
|
|
|
if (cur_distkeys == NULL)
|
|
return true;
|
|
|
|
ListCell* cell = NULL;
|
|
foreach (cell, cur_distkeys) {
|
|
diskey = (Node*)lfirst(cell);
|
|
|
|
foreach (lcell, joinclauses) {
|
|
/*
|
|
* We judge whether need to redistribute on one rel,
|
|
* which is match to be left_relids or right_relids of rinfo.
|
|
*/
|
|
RestrictInfo* rinfo = (RestrictInfo*)lfirst(lcell);
|
|
Relids suitable_relids = NULL;
|
|
EquivalenceClass* suitable_ec = NULL;
|
|
bool is_left = false;
|
|
|
|
if (rinfo->orclause || !rinfo->left_ec || !rinfo->right_ec)
|
|
continue;
|
|
|
|
if (!rinfo->left_relids || !rinfo->right_relids)
|
|
continue;
|
|
|
|
/*
|
|
* To op expr, we need judge args's hash arithmetic compatibility, if they are not compatible, we need
|
|
* redistribute or broadcast. For example, timestamp and date.
|
|
*/
|
|
if (IsA(rinfo->clause, OpExpr) && !is_args_type_compatible((OpExpr*)rinfo->clause)) {
|
|
continue;
|
|
}
|
|
|
|
if (bms_is_subset(rinfo->left_relids, side_relids) &&
|
|
(bms_is_subset(rinfo->right_relids, other_relids) || other_relids == NULL)) {
|
|
suitable_relids = rinfo->left_relids;
|
|
suitable_ec = rinfo->left_ec;
|
|
is_left = true;
|
|
} else if (bms_is_subset(rinfo->right_relids, side_relids) &&
|
|
(bms_is_subset(rinfo->left_relids, other_relids) || other_relids == NULL)) {
|
|
suitable_relids = rinfo->right_relids;
|
|
suitable_ec = rinfo->right_ec;
|
|
is_left = false;
|
|
}
|
|
|
|
if (is_ec_usable_for_join(suitable_relids, suitable_ec, diskey, rinfo->clause, is_left)) {
|
|
*rrinfo = lappend(*rrinfo, rinfo);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lcell == NULL) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* locate_distribute_key:
|
|
* get distribute key of join rel from desired key, outer and inner distribute key
|
|
* Parameters:
|
|
* @in jointype: join type of outerrel and innerrel
|
|
* @in outer_distributekey: distribute key of outerrel
|
|
* @in inner_distributekey: distribute key of innerrel
|
|
* @in desired_key: matching or superset key in upper level,
|
|
* which is desired in join distribute key
|
|
* @in exact_match: note whether in exact mode, and in this
|
|
* mode, we directly return desired_key
|
|
* Return:
|
|
* final distribute key for join rel
|
|
*/
|
|
List* locate_distribute_key(
|
|
JoinType jointype, List* outer_distributekey, List* inner_distributekey, List* desired_key, bool exact_match)
|
|
{
|
|
List* join_distributekey = NIL;
|
|
|
|
AssertEreport(JOIN_UNIQUE_INNER != jointype && JOIN_UNIQUE_OUTER != jointype && JOIN_FULL != jointype,
|
|
MOD_OPT_JOIN,
|
|
"Join type is not expected");
|
|
|
|
if (exact_match) {
|
|
AssertEreport(desired_key != NIL, MOD_OPT_JOIN, "Must have a desired key when exact match");
|
|
join_distributekey = desired_key;
|
|
} else if (jointype == JOIN_INNER && desired_key != NIL) {
|
|
ListCell* lc1 = NULL;
|
|
ListCell* lc2 = NULL;
|
|
forboth(lc1, outer_distributekey, lc2, inner_distributekey)
|
|
{
|
|
Node* n1 = (Node*)lfirst(lc1);
|
|
Node* n2 = (Node*)lfirst(lc2);
|
|
Node* desired_node = NULL;
|
|
|
|
if (list_member(desired_key, n2))
|
|
desired_node = n2;
|
|
else
|
|
desired_node = n1;
|
|
join_distributekey = lappend(join_distributekey, desired_node);
|
|
}
|
|
} else if (LHS_join(jointype))
|
|
join_distributekey = outer_distributekey;
|
|
else if (RHS_join(jointype))
|
|
join_distributekey = inner_distributekey;
|
|
|
|
return join_distributekey;
|
|
}
|
|
|
|
double get_skew_ratio(double distinct_value)
|
|
{
|
|
double dn_num;
|
|
|
|
if (distinct_value == 0)
|
|
dn_num = u_sess->pgxc_cxt.NumDataNodes;
|
|
else if (distinct_value < (double)u_sess->pgxc_cxt.NumDataNodes / 3)
|
|
dn_num = distinct_value;
|
|
else
|
|
dn_num = distinct_value - (distinct_value - (double)u_sess->pgxc_cxt.NumDataNodes / 3) * 2 / 3;
|
|
|
|
if (dn_num < 1)
|
|
dn_num = 1;
|
|
else if (dn_num > u_sess->pgxc_cxt.NumDataNodes)
|
|
dn_num = u_sess->pgxc_cxt.NumDataNodes;
|
|
|
|
return (double)u_sess->pgxc_cxt.NumDataNodes / dn_num;
|
|
}
|
|
|
|
/*
|
|
* get_redist_unique
|
|
* Compute cost and build redistribute + unique path
|
|
*/
|
|
Path* get_redist_unique(PlannerInfo* root, Path* path, StreamType stream_type, List* distribute_key, List* pathkeys,
|
|
double skew, Distribution* target_distribution, ParallelDesc* smpDesc, bool cost_only)
|
|
{
|
|
AssertEreport(IsA(path, UniquePath), MOD_OPT_JOIN, "Path should be UniquePath");
|
|
UniquePath* origin_path = (UniquePath*)path;
|
|
|
|
/* Get sub-path of the UniquePath */
|
|
Path* origin_subpath = NULL;
|
|
if (cost_only) {
|
|
origin_subpath = makeNode(Path);
|
|
origin_subpath->rows = origin_path->subpath->rows;
|
|
origin_subpath->multiple = origin_path->subpath->multiple;
|
|
origin_subpath->startup_cost = origin_path->subpath->startup_cost;
|
|
origin_subpath->total_cost = origin_path->subpath->total_cost;
|
|
origin_subpath->stream_cost = origin_path->subpath->stream_cost;
|
|
Distribution* distribution = ng_get_dest_distribution(origin_path->subpath);
|
|
ng_copy_distribution(&origin_subpath->distribution, distribution);
|
|
origin_subpath->locator_type = origin_path->subpath->locator_type;
|
|
} else {
|
|
origin_subpath = origin_path->subpath;
|
|
}
|
|
|
|
/* create a stream on the sub-path */
|
|
Path* stream_path = create_stream_path(root,
|
|
origin_path->subpath->parent,
|
|
stream_type,
|
|
distribute_key,
|
|
pathkeys,
|
|
(Path*)origin_subpath,
|
|
skew,
|
|
target_distribution,
|
|
smpDesc);
|
|
|
|
/* create a UniquePath on the stream */
|
|
UniquePath* newpath = makeNode(UniquePath);
|
|
newpath->path.pathtype = T_Unique;
|
|
newpath->path.parent = origin_path->path.parent;
|
|
newpath->path.param_info = origin_path->path.param_info;
|
|
newpath->path.pathkeys = stream_path->pathkeys;
|
|
|
|
#ifdef STREAMPLAN
|
|
inherit_path_locator_info((Path*)newpath, stream_path);
|
|
#endif
|
|
|
|
/* calculate final agg rows for agg operator */
|
|
unsigned int num_datanodes = ng_get_dest_num_data_nodes(stream_path);
|
|
newpath->path.rows = estimate_num_groups(
|
|
root, origin_path->uniq_exprs, origin_path->path.parent->rows, num_datanodes, STATS_TYPE_GLOBAL);
|
|
newpath->path.rows = clamp_row_est(newpath->path.rows);
|
|
newpath->path.multiple = 1.0;
|
|
double local_rows = PATH_LOCAL_ROWS(&newpath->path);
|
|
|
|
/*
|
|
* Assume the output is unsorted, since we don't necessarily have pathkeys
|
|
* to represent it. (This might get overridden below.)
|
|
*/
|
|
newpath->subpath = stream_path;
|
|
newpath->in_operators = origin_path->in_operators;
|
|
newpath->uniq_exprs = origin_path->uniq_exprs;
|
|
|
|
Path sort_path;
|
|
Path agg_path;
|
|
int numCols = list_length(origin_path->uniq_exprs);
|
|
|
|
OpMemInfo sort_mem_info, hash_mem_info;
|
|
errno_t rc = 0;
|
|
|
|
rc = memset_s(&sort_path, sizeof(Path), 0, sizeof(Path));
|
|
securec_check(rc, "\0", "\0");
|
|
rc = memset_s(&agg_path, sizeof(Path), 0, sizeof(Path));
|
|
securec_check(rc, "\0", "\0");
|
|
rc = memset_s(&sort_mem_info, sizeof(OpMemInfo), 0, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
rc = memset_s(&hash_mem_info, sizeof(OpMemInfo), 0, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
/* Estimate cost for hashaggregate */
|
|
if (origin_path->both_method || UNIQUE_PATH_HASH == origin_path->umethod) {
|
|
Size hashentrysize = 0;
|
|
|
|
if (root->glob->vectorized) {
|
|
hashentrysize = get_path_actual_total_width(stream_path, root->glob->vectorized, OP_HASHAGG, 0);
|
|
} else {
|
|
hashentrysize = get_hash_entry_size(origin_path->path.parent->width);
|
|
}
|
|
|
|
Distribution* distribution = ng_get_dest_distribution((Path*)newpath);
|
|
ng_copy_distribution(&agg_path.distribution, distribution);
|
|
cost_agg(&agg_path,
|
|
root,
|
|
AGG_HASHED,
|
|
NULL,
|
|
numCols,
|
|
local_rows,
|
|
stream_path->startup_cost,
|
|
stream_path->total_cost,
|
|
PATH_LOCAL_ROWS(stream_path),
|
|
origin_path->path.parent->width,
|
|
hashentrysize,
|
|
1,
|
|
&hash_mem_info);
|
|
}
|
|
|
|
/* Estimate cost for sort+unique implementation */
|
|
if (origin_path->both_method || UNIQUE_PATH_SORT == origin_path->umethod) {
|
|
int subpath_width = get_path_actual_total_width(stream_path, root->glob->vectorized, OP_SORT);
|
|
|
|
cost_sort(&sort_path,
|
|
NIL,
|
|
stream_path->total_cost,
|
|
PATH_LOCAL_ROWS(stream_path),
|
|
subpath_width,
|
|
0.0,
|
|
u_sess->opt_cxt.op_work_mem,
|
|
-1.0,
|
|
root->glob->vectorized,
|
|
1,
|
|
&sort_mem_info);
|
|
/*
|
|
* 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.) This should agree with
|
|
* make_unique.
|
|
*/
|
|
sort_path.total_cost += u_sess->attr.attr_sql.cpu_operator_cost * local_rows * numCols;
|
|
}
|
|
|
|
/* Determine how to make the data unique */
|
|
if (origin_path->both_method) {
|
|
if (agg_path.total_cost < sort_path.total_cost) {
|
|
newpath->umethod = UNIQUE_PATH_HASH;
|
|
} else {
|
|
newpath->umethod = UNIQUE_PATH_SORT;
|
|
}
|
|
} else {
|
|
newpath->umethod = origin_path->umethod;
|
|
}
|
|
|
|
if (UNIQUE_PATH_HASH == newpath->umethod) {
|
|
if (!cost_only) {
|
|
origin_path->hold_tlist = true;
|
|
}
|
|
newpath->path.startup_cost = agg_path.startup_cost;
|
|
newpath->path.total_cost = agg_path.total_cost;
|
|
rc = memcpy_s(&newpath->mem_info, sizeof(OpMemInfo), &hash_mem_info, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
} else if (UNIQUE_PATH_SORT == newpath->umethod) {
|
|
newpath->path.startup_cost = sort_path.startup_cost;
|
|
newpath->path.total_cost = sort_path.total_cost;
|
|
rc = memcpy_s(&newpath->mem_info, sizeof(OpMemInfo), &sort_mem_info, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
} else {
|
|
newpath->path.startup_cost = stream_path->startup_cost;
|
|
newpath->path.total_cost = stream_path->total_cost;
|
|
}
|
|
|
|
newpath->path.stream_cost = stream_path->stream_cost;
|
|
return (Path*)newpath;
|
|
}
|
|
|
|
/*
|
|
* get_unique_redist
|
|
* Compute cost and build unique + redistribute path
|
|
*/
|
|
Path* get_unique_redist(PlannerInfo* root, Path* path, StreamType stream_type, List* distribute_key, List* pathkeys,
|
|
double skew, Distribution* target_distribution, ParallelDesc* smpDesc)
|
|
{
|
|
AssertEreport(IsA(path, UniquePath), MOD_OPT_JOIN, "Path should be UniquePath");
|
|
UniquePath* origin_path = (UniquePath*)path;
|
|
|
|
Path* stream_path = create_stream_path(root,
|
|
origin_path->path.parent,
|
|
stream_type,
|
|
distribute_key,
|
|
pathkeys,
|
|
(Path*)origin_path,
|
|
skew,
|
|
target_distribution,
|
|
smpDesc);
|
|
|
|
return stream_path;
|
|
}
|
|
|
|
/*
|
|
* get_unique_redist_unique
|
|
* Compute cost and build unique + redistribute + unique path
|
|
*/
|
|
Path* get_unique_redist_unique(PlannerInfo* root, Path* path, StreamType stream_type, List* distribute_key,
|
|
List* pathkeys, double skew, Distribution* target_distribution, ParallelDesc* smpDesc, bool cost_only)
|
|
{
|
|
AssertEreport(IsA(path, UniquePath), MOD_OPT_JOIN, "Path should be UniquePath");
|
|
UniquePath* origin_path = (UniquePath*)path;
|
|
Path* stream_path = NULL;
|
|
UniquePath* newpath = NULL;
|
|
RelOptInfo* rel = origin_path->path.parent;
|
|
int numCols = list_length(origin_path->uniq_exprs);
|
|
Path sort_path;
|
|
Path agg_path;
|
|
double local_rows;
|
|
double local_distinct;
|
|
errno_t rc = 0;
|
|
|
|
rc = memset_s(&sort_path, sizeof(Path), 0, sizeof(Path));
|
|
securec_check(rc, "\0", "\0");
|
|
rc = memset_s(&agg_path, sizeof(Path), 0, sizeof(Path));
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
/* re-estimate rows of first agg */
|
|
double numdistinct[2] = {1, 1};
|
|
get_num_distinct(root,
|
|
origin_path->uniq_exprs,
|
|
RELOPTINFO_LOCAL_FIELD(root, origin_path->path.parent, rows),
|
|
origin_path->path.parent->rows,
|
|
ng_get_dest_num_data_nodes(root, rel),
|
|
numdistinct,
|
|
NULL);
|
|
|
|
local_distinct = estimate_agg_num_distinct(root, origin_path->uniq_exprs, (Path*)origin_path->subpath, numdistinct);
|
|
|
|
Size first_hashentrysize = 0;
|
|
|
|
if (root->glob->vectorized) {
|
|
first_hashentrysize = get_path_actual_total_width((Path*)origin_path, root->glob->vectorized, OP_HASHAGG, 0);
|
|
} else {
|
|
first_hashentrysize = get_hash_entry_size(rel->width);
|
|
}
|
|
|
|
if (UNIQUE_PATH_HASH == origin_path->umethod) {
|
|
OpMemInfo origin_hash_mem_info;
|
|
double multiple = ((Path*)origin_path)->multiple;
|
|
|
|
rc = memset_s(&origin_hash_mem_info, sizeof(OpMemInfo), 0, sizeof(OpMemInfo));
|
|
securec_check_c(rc, "\0", "\0");
|
|
cost_agg((Path*)origin_path,
|
|
root,
|
|
AGG_HASHED,
|
|
NULL,
|
|
numCols,
|
|
local_distinct,
|
|
origin_path->subpath->startup_cost,
|
|
origin_path->subpath->total_cost,
|
|
PATH_LOCAL_ROWS((Path*)origin_path->subpath),
|
|
origin_path->subpath->parent->width,
|
|
first_hashentrysize,
|
|
origin_path->path.dop,
|
|
&origin_hash_mem_info);
|
|
/* cost_agg will set multiple to 1.0, so restore it */
|
|
((Path*)origin_path)->multiple = multiple;
|
|
rc = memcpy_s(&origin_path->mem_info, sizeof(OpMemInfo), &origin_hash_mem_info, sizeof(OpMemInfo));
|
|
securec_check_c(rc, "\0", "\0");
|
|
} else if (UNIQUE_PATH_SORT == origin_path->umethod) {
|
|
OpMemInfo origin_sort_mem_info;
|
|
|
|
rc = memset_s(&origin_sort_mem_info, sizeof(OpMemInfo), 0, sizeof(OpMemInfo));
|
|
securec_check_c(rc, "\0", "\0");
|
|
cost_sort((Path*)origin_path,
|
|
NIL,
|
|
origin_path->subpath->total_cost,
|
|
PATH_LOCAL_ROWS((Path*)origin_path->subpath),
|
|
origin_path->subpath->parent->width,
|
|
0.0,
|
|
u_sess->opt_cxt.op_work_mem,
|
|
-1.0,
|
|
root->glob->vectorized,
|
|
origin_path->path.dop,
|
|
&origin_sort_mem_info);
|
|
rc = memcpy_s(&origin_path->mem_info, sizeof(OpMemInfo), &origin_sort_mem_info, sizeof(OpMemInfo));
|
|
securec_check_c(rc, "\0", "\0");
|
|
}
|
|
|
|
/* add the redistribute node */
|
|
stream_path = create_stream_path(root,
|
|
origin_path->path.parent,
|
|
stream_type,
|
|
distribute_key,
|
|
pathkeys,
|
|
(Path*)origin_path,
|
|
skew,
|
|
target_distribution,
|
|
smpDesc);
|
|
|
|
newpath = makeNode(UniquePath);
|
|
newpath->path.pathtype = T_Unique;
|
|
newpath->path.parent = origin_path->path.parent;
|
|
newpath->path.param_info = origin_path->path.param_info;
|
|
newpath->path.pathkeys = stream_path->pathkeys;
|
|
|
|
#ifdef STREAMPLAN
|
|
inherit_path_locator_info((Path*)newpath, stream_path);
|
|
#endif
|
|
|
|
/* calculate final agg rows for agg operator */
|
|
unsigned int num_datanodes = ng_get_dest_num_data_nodes(stream_path);
|
|
newpath->path.rows = estimate_num_groups(
|
|
root, origin_path->uniq_exprs, origin_path->path.parent->rows, num_datanodes, STATS_TYPE_GLOBAL);
|
|
newpath->path.rows = clamp_row_est(newpath->path.rows);
|
|
newpath->path.multiple = 1.0;
|
|
local_rows = PATH_LOCAL_ROWS(&newpath->path);
|
|
|
|
/*
|
|
* Assume the output is unsorted, since we don't necessarily have pathkeys
|
|
* to represent it. (This might get overridden below.)
|
|
*/
|
|
newpath->subpath = stream_path;
|
|
newpath->in_operators = origin_path->in_operators;
|
|
newpath->uniq_exprs = origin_path->uniq_exprs;
|
|
|
|
OpMemInfo sort_mem_info, hash_mem_info;
|
|
|
|
rc = memset_s(&sort_mem_info, sizeof(OpMemInfo), 0, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
rc = memset_s(&hash_mem_info, sizeof(OpMemInfo), 0, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
/* Estimate cost for hashaggregate */
|
|
if (origin_path->both_method || UNIQUE_PATH_HASH == origin_path->umethod) {
|
|
Size hashentrysize = 0;
|
|
|
|
if (root->glob->vectorized) {
|
|
hashentrysize = get_path_actual_total_width(stream_path, root->glob->vectorized, OP_HASHAGG, 0);
|
|
} else {
|
|
hashentrysize = get_hash_entry_size(rel->width);
|
|
}
|
|
|
|
Distribution* distribution = ng_get_dest_distribution((Path*)newpath);
|
|
ng_copy_distribution(&agg_path.distribution, distribution);
|
|
cost_agg(&agg_path,
|
|
root,
|
|
AGG_HASHED,
|
|
NULL,
|
|
numCols,
|
|
local_rows,
|
|
stream_path->startup_cost,
|
|
stream_path->total_cost,
|
|
PATH_LOCAL_ROWS(stream_path),
|
|
rel->width,
|
|
hashentrysize,
|
|
1,
|
|
&hash_mem_info);
|
|
}
|
|
|
|
/* Estimate cost for sort+unique implementation */
|
|
if (origin_path->both_method || UNIQUE_PATH_SORT == origin_path->umethod) {
|
|
int subpath_width = get_path_actual_total_width(stream_path, root->glob->vectorized, OP_SORT);
|
|
|
|
cost_sort(&sort_path,
|
|
NIL,
|
|
stream_path->total_cost,
|
|
PATH_LOCAL_ROWS(stream_path),
|
|
subpath_width,
|
|
0.0,
|
|
u_sess->opt_cxt.op_work_mem,
|
|
-1.0,
|
|
root->glob->vectorized,
|
|
1,
|
|
&sort_mem_info);
|
|
/*
|
|
* 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.) This should agree with
|
|
* make_unique.
|
|
*/
|
|
sort_path.total_cost += u_sess->attr.attr_sql.cpu_operator_cost * local_rows * numCols;
|
|
}
|
|
|
|
/* Determine how to make the data unique */
|
|
if (origin_path->both_method) {
|
|
if (agg_path.total_cost < sort_path.total_cost) {
|
|
newpath->umethod = UNIQUE_PATH_HASH;
|
|
} else {
|
|
newpath->umethod = UNIQUE_PATH_SORT;
|
|
}
|
|
} else {
|
|
newpath->umethod = origin_path->umethod;
|
|
}
|
|
|
|
if (UNIQUE_PATH_HASH == newpath->umethod) {
|
|
if (!cost_only) {
|
|
origin_path->hold_tlist = true;
|
|
}
|
|
newpath->path.startup_cost = agg_path.startup_cost;
|
|
newpath->path.total_cost = agg_path.total_cost;
|
|
rc = memcpy_s(&newpath->mem_info, sizeof(OpMemInfo), &hash_mem_info, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
} else if (UNIQUE_PATH_SORT == newpath->umethod) {
|
|
newpath->path.startup_cost = sort_path.startup_cost;
|
|
newpath->path.total_cost = sort_path.total_cost;
|
|
rc = memcpy_s(&newpath->mem_info, sizeof(OpMemInfo), &sort_mem_info, sizeof(OpMemInfo));
|
|
securec_check(rc, "\0", "\0");
|
|
} else {
|
|
newpath->path.startup_cost = stream_path->startup_cost;
|
|
newpath->path.total_cost = stream_path->total_cost;
|
|
}
|
|
|
|
newpath->path.stream_cost = stream_path->stream_cost;
|
|
return (Path*)newpath;
|
|
}
|
|
|
|
/*
|
|
* get_redist_unique_redist_unique
|
|
* Compute cost and build redistribute + unique + redistribute + unique path
|
|
*/
|
|
Path* get_redist_unique_redist_unique(PlannerInfo* root, Path* path, StreamType stream_type, List* distribute_key,
|
|
List* pathkeys, double skew, Distribution* target_distribution, ParallelDesc* smpDesc, bool cost_only)
|
|
{
|
|
/*
|
|
* step 1: get less skewed distribute keys
|
|
* Generate less skew distribute key for potential shuffle
|
|
*/
|
|
List* final_list_exprs = NULL;
|
|
if (Abs(path->multiple - 1.0) < 0.001 && NULL != path->distribute_keys) {
|
|
final_list_exprs = path->distribute_keys;
|
|
} else {
|
|
final_list_exprs = path->parent->reltargetlist;
|
|
}
|
|
double multiple_less_skew = 0.0;
|
|
List* distribute_key_less_skew =
|
|
get_distributekey_from_tlist(root, NIL, final_list_exprs, path->rows, &multiple_less_skew);
|
|
|
|
/*
|
|
* We can not generate this kind of path when:
|
|
* (1) a less skewed distribute key could not be found
|
|
* (2) less skewed distribute key is not good enough
|
|
* (3) less skewed distribute key is same as original distribute key
|
|
*/
|
|
if (NULL == distribute_key_less_skew || multiple_less_skew > skew ||
|
|
equal_distributekey(root, distribute_key, distribute_key_less_skew)) {
|
|
return NULL;
|
|
}
|
|
|
|
/* step 2: redistribute to the less skewed distribute keys */
|
|
Path* path_stage_1 = get_redist_unique(root,
|
|
path,
|
|
stream_type,
|
|
distribute_key_less_skew,
|
|
pathkeys,
|
|
multiple_less_skew,
|
|
target_distribution,
|
|
smpDesc,
|
|
cost_only);
|
|
|
|
/* step 3: do normal unique */
|
|
Path* path_stage_2 = get_unique_redist_unique(
|
|
root, path_stage_1, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc, cost_only);
|
|
|
|
return path_stage_2;
|
|
}
|
|
|
|
/*
|
|
* get_optimal_join_unique_path
|
|
* get the best join unique path method
|
|
*/
|
|
SJoinUniqueMethod get_optimal_join_unique_path(PlannerInfo* root, Path* path, StreamType stream_type,
|
|
List* distribute_key, List* pathkeys, double skew, Distribution* target_distribution, ParallelDesc* smpDesc)
|
|
{
|
|
AssertEreport(IsA(path, UniquePath), MOD_OPT_JOIN, "Path should be UniquePath");
|
|
UniquePath* origin_path = (UniquePath*)path;
|
|
Path* newpath = NULL;
|
|
SJoinUniqueMethod option = UNIQUE_REDISTRIBUTE_UNIQUE;
|
|
Cost best_cost = 0.0;
|
|
|
|
/*
|
|
* analyze stream reason, two possible reasons
|
|
* (1) because of unmatched distribute key or smp
|
|
* (2) because of unmatched node group
|
|
*/
|
|
bool stream_reason_distkey_smp = (smpDesc && smpDesc->distriType != PARALLEL_NONE) ||
|
|
needs_agg_stream(root, distribute_key, origin_path->path.distribute_keys);
|
|
bool stream_reason_nodegroup = !ng_is_same_group(ng_get_dest_distribution(path), target_distribution);
|
|
|
|
/* Path 1: redistribute + unique */
|
|
newpath =
|
|
get_redist_unique(root, path, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc, true);
|
|
|
|
ereport(
|
|
DEBUG1, (errmodule(MOD_OPT_JOIN), errmsg("[Join Unique] Path method 1: total_cost %lf.", newpath->total_cost)));
|
|
|
|
if (best_cost < 0.0001 || newpath->total_cost < best_cost) {
|
|
best_cost = newpath->total_cost;
|
|
option = REDISTRIBUTE_UNIQUE;
|
|
}
|
|
|
|
/*
|
|
* Path 2: unique + redistribute
|
|
* This path only support unmatched node group.
|
|
*/
|
|
if (!stream_reason_distkey_smp && stream_reason_nodegroup) {
|
|
newpath =
|
|
get_unique_redist(root, path, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc);
|
|
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN), errmsg("[Join Unique] Path method 2: total_cost %lf.", newpath->total_cost)));
|
|
|
|
if (best_cost < 0.0001 || newpath->total_cost < best_cost) {
|
|
best_cost = newpath->total_cost;
|
|
option = UNIQUE_REDISTRIBUTE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Path 3: unique + redistribute + unique
|
|
* This path only support unmatched distribute key
|
|
*/
|
|
if (stream_reason_distkey_smp && !stream_reason_nodegroup) {
|
|
newpath = get_unique_redist_unique(
|
|
root, path, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc, true);
|
|
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN), errmsg("[Join Unique] Path method 3: total_cost %lf.", newpath->total_cost)));
|
|
|
|
if (best_cost < 0.0001 || newpath->total_cost < best_cost) {
|
|
best_cost = newpath->total_cost;
|
|
option = UNIQUE_REDISTRIBUTE_UNIQUE;
|
|
}
|
|
}
|
|
|
|
/* Path 4: redistribute + unique + redistribute + unique */
|
|
newpath = get_redist_unique_redist_unique(
|
|
root, path, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc, true);
|
|
if (NULL != newpath) {
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN), errmsg("[Join Unique] Path method 4: total_cost %lf.", newpath->total_cost)));
|
|
}
|
|
if (NULL != newpath && (best_cost < 0.0001 || newpath->total_cost < best_cost) && best_cost < NG_FORBIDDEN_COST) {
|
|
best_cost = newpath->total_cost;
|
|
option = REDISTRIBUTE_UNIQUE_REDISTRIBUTE_UNIQUE;
|
|
}
|
|
|
|
return option;
|
|
}
|
|
|
|
/*
|
|
* make_join_unique_path
|
|
* make a join unique path
|
|
* (1) get the optimal method for join unique base on cost
|
|
* (2) build a path base on the optimal method
|
|
*/
|
|
static Path* make_join_unique_path(PlannerInfo* root, Path* path, StreamType stream_type, List* distribute_key,
|
|
List* pathkeys, double skew, Distribution* target_distribution, ParallelDesc* smpDesc)
|
|
{
|
|
/* Get the optimal method to make this join unuque path */
|
|
SJoinUniqueMethod option = get_optimal_join_unique_path(
|
|
root, path, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc);
|
|
|
|
ereport(DEBUG1, (errmodule(MOD_OPT_JOIN), errmsg("[Join Unique] Best path method is No. %d.", option + 1)));
|
|
|
|
/* Make this join unique path */
|
|
Path* best_path = NULL;
|
|
switch (option) {
|
|
case REDISTRIBUTE_UNIQUE:
|
|
best_path = get_redist_unique(
|
|
root, path, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc);
|
|
break;
|
|
case UNIQUE_REDISTRIBUTE:
|
|
best_path = get_unique_redist(
|
|
root, path, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc);
|
|
break;
|
|
case UNIQUE_REDISTRIBUTE_UNIQUE:
|
|
best_path = get_unique_redist_unique(
|
|
root, path, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc);
|
|
break;
|
|
case REDISTRIBUTE_UNIQUE_REDISTRIBUTE_UNIQUE:
|
|
best_path = get_redist_unique_redist_unique(
|
|
root, path, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc);
|
|
break;
|
|
}
|
|
|
|
if (best_path != NULL)
|
|
ereport(DEBUG1,
|
|
(errmodule(MOD_OPT_JOIN),
|
|
errmsg("[Join Unique] Finish building path, final startup cost : %lf, final total cost : %lf.",
|
|
best_path->startup_cost,
|
|
best_path->total_cost)));
|
|
|
|
return best_path;
|
|
}
|
|
|
|
/*
|
|
* Add a Stream path on a path
|
|
*/
|
|
Path* stream_side_path(PlannerInfo* root, Path* path, JoinType jointype, bool is_replicate, StreamType stream_type,
|
|
List* distribute_key, List* pathkeys, bool is_inner, double skew, Distribution* target_distribution,
|
|
ParallelDesc* smpDesc)
|
|
{
|
|
|
|
/* target_distribution not likey to be NULL, but let's play safe */
|
|
if (NULL == target_distribution) {
|
|
target_distribution = NewDistribution();
|
|
Distribution* distribution = ng_get_dest_distribution(path);
|
|
ng_set_distribution(target_distribution, distribution);
|
|
}
|
|
|
|
if (is_replicate) {
|
|
if (STREAM_BROADCAST == stream_type) {
|
|
/*
|
|
* If a STREAM_BROADCAST above a replicate table and node group changed,
|
|
* we still need to shuffle it.
|
|
* Else, return original path directly.
|
|
*/
|
|
if (ng_is_shuffle_needed(root, path, target_distribution)) {
|
|
return create_stream_path(
|
|
root, path->parent, STREAM_BROADCAST, NIL, pathkeys, path, skew, target_distribution, smpDesc);
|
|
} else {
|
|
return path;
|
|
}
|
|
} else if (STREAM_REDISTRIBUTE == stream_type) {
|
|
return create_stream_path(root,
|
|
path->parent,
|
|
STREAM_REDISTRIBUTE,
|
|
distribute_key,
|
|
pathkeys,
|
|
path,
|
|
skew,
|
|
target_distribution,
|
|
smpDesc);
|
|
}
|
|
} else {
|
|
if ((JOIN_UNIQUE_INNER == jointype && is_inner) || (JOIN_UNIQUE_OUTER == jointype && !is_inner)) {
|
|
return make_join_unique_path(
|
|
root, path, stream_type, distribute_key, pathkeys, skew, target_distribution, smpDesc);
|
|
} else {
|
|
return create_stream_path(
|
|
root, path->parent, stream_type, distribute_key, pathkeys, path, skew, target_distribution, smpDesc);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
double get_node_mcf(PlannerInfo* root, Node* v, double rows)
|
|
{
|
|
VariableStatData vardata;
|
|
/* u_sess->pgxc_cxt.NumDataNodes is used for default value of mcvfreq */
|
|
double mcvfreq = pow(u_sess->pgxc_cxt.NumDataNodes, (double)1 / 3) / u_sess->pgxc_cxt.NumDataNodes;
|
|
float4* numbers = NULL;
|
|
int nnumbers = 0;
|
|
|
|
examine_variable(root, v, 0, &vardata);
|
|
|
|
/*
|
|
* Look up the frequency of the most common value, if available.
|
|
*/
|
|
if (HeapTupleIsValid(vardata.statsTuple)) {
|
|
Form_pg_statistic stats;
|
|
stats = (Form_pg_statistic)GETSTRUCT(vardata.statsTuple);
|
|
mcvfreq = 0.0;
|
|
|
|
if (get_attstatsslot(vardata.statsTuple,
|
|
vardata.atttype,
|
|
vardata.atttypmod,
|
|
STATISTIC_KIND_MCV,
|
|
InvalidOid,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&numbers,
|
|
&nnumbers)) {
|
|
double relrows;
|
|
|
|
/*
|
|
* The first MCV stat is for the most common value.
|
|
*/
|
|
if (nnumbers > 0)
|
|
mcvfreq = numbers[0];
|
|
|
|
/*
|
|
* for total rows large than rel rows in coalesce expr, we think null
|
|
* may be added, so adjust biase value
|
|
*/
|
|
relrows = vardata.rel->rows;
|
|
if (relrows < rows) {
|
|
mcvfreq *= clamp_row_est(rows) / clamp_row_est(relrows);
|
|
if (mcvfreq > 1.0)
|
|
mcvfreq = 1.0;
|
|
}
|
|
|
|
free_attstatsslot(vardata.atttype, NULL, 0, numbers, nnumbers);
|
|
}
|
|
|
|
mcvfreq = Max(stats->stanullfrac, mcvfreq);
|
|
}
|
|
|
|
ReleaseVariableStats(vardata);
|
|
return mcvfreq;
|
|
}
|
|
|
|
/*
|
|
* is_exact_match_keys_full:
|
|
* Judge if every item of matching keys has a available distribute key.
|
|
* In matching key mode, we should ganrantee that, or the distribute
|
|
* key can't match to matching key
|
|
* Parameters:
|
|
* @in match_keys: matching key record array from upper level
|
|
* @in length: the length of record array
|
|
* Return:
|
|
* true if all the matching key location is occupied
|
|
*/
|
|
bool is_exact_match_keys_full(Node** match_keys, int length)
|
|
{
|
|
int i;
|
|
for (i = 0; i < length; i++) {
|
|
if (match_keys[i] == NULL)
|
|
break;
|
|
}
|
|
return (i == length);
|
|
}
|
|
|
|
/*
|
|
* get_distribute_node:
|
|
* get single distribute key from one restrictinfo
|
|
* Parameters:
|
|
* @in root: planner info of current query level
|
|
* @in rinfo: current restrictinfo, should be equal condition
|
|
* @in parent_rel: current rel used to find distribute key
|
|
* @in local_left: whether the current rel is located in left side
|
|
* @out skew_multiple: the multiple of distribute key founded
|
|
* @in desired_keys: upper matching key that distribute key should be found according to
|
|
* @in exact_match_keys: In matching key mode, we should record the distribute key of
|
|
* every matching key, this array is used to do the record
|
|
* Return:
|
|
* distribute key found from the restrictinfo, or NULL
|
|
*/
|
|
Node* get_distribute_node(PlannerInfo* root, RestrictInfo* rinfo, RelOptInfo* parent_rel, bool local_left,
|
|
double* skew_multiple, List* desired_keys, Node** exact_match_keys)
|
|
{
|
|
#define MARKED_MATCHED_NODE (Node*)0x1
|
|
ListCell* lc1 = NULL;
|
|
ListCell* lc2 = NULL;
|
|
ListCell* lc3 = NULL;
|
|
Node* match_var = NULL;
|
|
Node* match_expr = NULL;
|
|
Node* joinkey = NULL;
|
|
/* We should match desired keys if exists */
|
|
bool desired_matched = (desired_keys != NIL) ? false : true;
|
|
/* When exact match, we should delete matched items fro desired keys, so make a copy */
|
|
List* desired_keys_copy = exact_match_keys != NULL ? list_copy(desired_keys) : NIL;
|
|
|
|
EquivalenceClass* oeclass = NULL;
|
|
|
|
if (local_left) {
|
|
oeclass = rinfo->left_ec;
|
|
joinkey = join_clause_get_join_key((Node*)rinfo->clause, true);
|
|
} else {
|
|
oeclass = rinfo->right_ec;
|
|
joinkey = join_clause_get_join_key((Node*)rinfo->clause, false);
|
|
}
|
|
|
|
AssertEreport(rinfo->orclause == NULL && oeclass != NULL,
|
|
MOD_OPT_JOIN,
|
|
"Restrictinfo should be equal join condition without or clause");
|
|
|
|
foreach (lc1, oeclass->ec_members) {
|
|
EquivalenceMember* em = (EquivalenceMember*)lfirst(lc1);
|
|
Node* nem = (Node*)em->em_expr;
|
|
Oid datatype = exprType(nem);
|
|
List* vars = NIL;
|
|
Relids relIds;
|
|
|
|
if (!OidIsValid(datatype) || !IsTypeDistributable(datatype))
|
|
continue;
|
|
|
|
/*
|
|
* check if the choosen diskey (which might come from an eclass)
|
|
* have compatiable type for hashing with the join key.
|
|
*/
|
|
if (!is_diskey_and_joinkey_compatible(nem, joinkey)) {
|
|
continue;
|
|
}
|
|
|
|
/* For desired key match, we should first set the matched item */
|
|
if (desired_keys != NIL) {
|
|
int i = 0;
|
|
bool matched = false;
|
|
foreach (lc2, desired_keys) {
|
|
/*
|
|
* If found, first use a note to record it, and
|
|
* then replace with final decided distribute key
|
|
*/
|
|
if (equal(lfirst(lc2), nem)) {
|
|
if (exact_match_keys != NULL)
|
|
exact_match_keys[i] = MARKED_MATCHED_NODE;
|
|
matched = true;
|
|
}
|
|
i++;
|
|
}
|
|
if (matched) {
|
|
/*
|
|
* Delete matched item from desired keys, NIL can be passed into
|
|
* this function so no need to judge if it's exact match case
|
|
*/
|
|
desired_keys_copy = list_delete(desired_keys_copy, nem);
|
|
desired_matched = true;
|
|
}
|
|
}
|
|
|
|
relIds = pull_varnos(nem);
|
|
if (bms_is_empty(relIds) || !bms_is_subset(relIds, parent_rel->relids)) {
|
|
bms_free_ext(relIds);
|
|
continue;
|
|
}
|
|
bms_free_ext(relIds);
|
|
|
|
if (list_member(parent_rel->reltargetlist, nem)) {
|
|
match_var = nem;
|
|
} else if (match_var == NULL) {
|
|
/*
|
|
* Check if all vars in sub targetlist
|
|
*
|
|
* For coalesce column in target list, it will presented as a Place Holder,
|
|
* so we will leave it as it is without expand it.
|
|
*/
|
|
vars = pull_var_clause(nem, PVC_REJECT_AGGREGATES, PVC_INCLUDE_PLACEHOLDERS);
|
|
foreach (lc2, vars) {
|
|
Node* node = (Node*)lfirst(lc2);
|
|
foreach (lc3, parent_rel->reltargetlist) {
|
|
Node* te = (Node*)lfirst(lc3);
|
|
if ((IsA(te, Var) && _equalSimpleVar((Var*)te, node)) || (!IsA(te, Var) && equal(te, node))) {
|
|
break;
|
|
}
|
|
}
|
|
if (lc3 == NULL) /* doesn't find the same in sub target list */
|
|
break;
|
|
}
|
|
list_free_ext(vars);
|
|
if (lc2 != NULL) { /* not all vars in sub targetlist */
|
|
continue;
|
|
} else if (match_expr == NULL) {
|
|
match_expr = nem;
|
|
}
|
|
}
|
|
|
|
/* find the matched item, but should have further in desired key match case */
|
|
if (match_var != NULL) {
|
|
/* For non exact match case, break when desired_matched is true */
|
|
if (exact_match_keys == NULL && desired_matched) {
|
|
break;
|
|
}
|
|
|
|
/* For exact match case, we should traverse until all desired keys match */
|
|
if (exact_match_keys != NULL && desired_keys_copy == NIL) {
|
|
AssertEreport(desired_matched, MOD_OPT_JOIN, "Desired keys should be matched");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (match_var == NULL) {
|
|
match_var = match_expr;
|
|
}
|
|
|
|
if (desired_matched && match_var != NULL) {
|
|
if (exact_match_keys != NULL) {
|
|
/* Then replace marked items with real distribute key */
|
|
for (int i = 0; i < list_length(desired_keys); i++) {
|
|
if (exact_match_keys[i] == MARKED_MATCHED_NODE)
|
|
exact_match_keys[i] = match_var;
|
|
}
|
|
list_free_ext(desired_keys_copy);
|
|
} else {
|
|
List* diskey = list_make1(match_var);
|
|
*skew_multiple = get_multiple_by_distkey(root, diskey, parent_rel->rows);
|
|
list_free_ext(diskey);
|
|
}
|
|
return match_var;
|
|
} else {
|
|
if (exact_match_keys != NULL) {
|
|
/* There's no match, so we should set all the match keys to NULL */
|
|
for (int i = 0; i < list_length(desired_keys); i++) {
|
|
if (exact_match_keys[i] == MARKED_MATCHED_NODE)
|
|
exact_match_keys[i] = NULL;
|
|
}
|
|
list_free_ext(desired_keys_copy);
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* get_distribute_keys:
|
|
* Get a final distribute key from the join clauses
|
|
* Parameters:
|
|
* @in root: planner info of current query level
|
|
* @in joinclauses: join clauses that two join rel uses
|
|
* @in outer_path: path of outer join rel
|
|
* @in inner_path: path of inner join rel
|
|
* @out skew_outer: skew multiple of outer distribute key
|
|
* @out skew_inner: skew multiple of inner distribute key
|
|
* @out distribute_keys_outer: returned outer distribute key
|
|
* @out distribute_keys_inner: returned inner distribute key
|
|
* @in desired_keys: desired key that try to meet
|
|
* @in exact_match: if there's a desired key, whether we should do exact match
|
|
*/
|
|
void get_distribute_keys(PlannerInfo* root, List* joinclauses, Path* outer_path, Path* inner_path, double* skew_outer,
|
|
double* skew_inner, List** distribute_keys_outer, List** distribute_keys_inner, List* desired_keys,
|
|
bool exact_match)
|
|
{
|
|
ListCell* cell = NULL;
|
|
RelOptInfo* outerrel = outer_path->parent;
|
|
RelOptInfo* innerrel = inner_path->parent;
|
|
bool locate_left_inner = true;
|
|
bool locate_left_outer = true;
|
|
double min_skew = -1.0;
|
|
Node* tmp_inner = NULL;
|
|
Node* tmp_outer = NULL;
|
|
Node* better_inner_key = NULL;
|
|
Node* better_outer_key = NULL;
|
|
List* disKeyInner = NULL;
|
|
List* disKeyOuter = NULL;
|
|
double skew_multiple_inner = 0.0;
|
|
double skew_multiple_outer = 0.0;
|
|
/* In exact match case, we should record distribute key location in both side */
|
|
Node** exact_match_keys_outer = exact_match ? (Node**)palloc0(list_length(desired_keys) * sizeof(Node*)) : NULL;
|
|
Node** exact_match_keys_inner = exact_match ? (Node**)palloc0(list_length(desired_keys) * sizeof(Node*)) : NULL;
|
|
|
|
foreach (cell, joinclauses) {
|
|
RestrictInfo* rinfo = (RestrictInfo*)lfirst(cell);
|
|
|
|
locate_left_inner = false;
|
|
locate_left_outer = false;
|
|
|
|
/*
|
|
* Unsupported redistribution joinclauss must be filtered,
|
|
* and assign locator info.
|
|
*/
|
|
if (rinfo->orclause || !rinfo->left_ec || !rinfo->right_ec)
|
|
continue;
|
|
|
|
if (!rinfo->left_relids || !rinfo->right_relids)
|
|
continue;
|
|
|
|
/*
|
|
* To op expr, we need judge args's hash arithmetic compatibility, if they are not compatible, we can not
|
|
* choose it as distribute key. For example, timestamp '' and date '', they values is same but hash value is
|
|
* different.
|
|
*/
|
|
if (IsA(rinfo->clause, OpExpr)) {
|
|
if (!is_args_type_compatible((OpExpr*)rinfo->clause)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Find the var in left and right of rinfo comes from which side */
|
|
if (bms_is_subset(rinfo->left_relids, innerrel->relids) &&
|
|
bms_is_subset(rinfo->right_relids, outerrel->relids)) {
|
|
locate_left_inner = true;
|
|
locate_left_outer = false;
|
|
} else if (bms_is_subset(rinfo->right_relids, innerrel->relids) &&
|
|
bms_is_subset(rinfo->left_relids, outerrel->relids)) {
|
|
locate_left_inner = false;
|
|
locate_left_outer = true;
|
|
}
|
|
|
|
/* Get a single distribute item from current join clause */
|
|
tmp_inner = get_distribute_node(
|
|
root, rinfo, innerrel, !locate_left_outer, &skew_multiple_inner, desired_keys, exact_match_keys_inner);
|
|
tmp_outer = get_distribute_node(
|
|
root, rinfo, outerrel, !locate_left_inner, &skew_multiple_outer, desired_keys, exact_match_keys_outer);
|
|
|
|
/* Distribute key is found */
|
|
if (tmp_inner != NULL && tmp_outer != NULL) {
|
|
/* For exact match case, we should match all keys and quit */
|
|
if (exact_match) {
|
|
if (is_exact_match_keys_full(exact_match_keys_outer, list_length(desired_keys)) &&
|
|
is_exact_match_keys_full(exact_match_keys_inner, list_length(desired_keys)))
|
|
break;
|
|
} else {
|
|
disKeyInner = lappend(disKeyInner, tmp_inner);
|
|
disKeyOuter = lappend(disKeyOuter, tmp_outer);
|
|
|
|
/* If overall multiple is less than formal ones, record new */
|
|
if (min_skew == -1.0 || skew_multiple_inner * skew_multiple_outer < min_skew) {
|
|
min_skew = skew_multiple_inner * skew_multiple_outer;
|
|
better_inner_key = tmp_inner;
|
|
better_outer_key = tmp_outer;
|
|
*skew_inner = skew_multiple_inner;
|
|
*skew_outer = skew_multiple_outer;
|
|
}
|
|
/* If there's no skew of distribute key, break */
|
|
if (skew_multiple_inner <= 1.0 && skew_multiple_outer <= 1.0)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* In exact match case, if not exact match, return NIL; or return the exact match distribute key */
|
|
if (exact_match) {
|
|
if (!is_exact_match_keys_full(exact_match_keys_outer, list_length(desired_keys)) ||
|
|
!is_exact_match_keys_full(exact_match_keys_inner, list_length(desired_keys))) {
|
|
pfree_ext(exact_match_keys_outer);
|
|
pfree_ext(exact_match_keys_inner);
|
|
return;
|
|
}
|
|
/* When all the match keys are exact match, we make the list and later calculate the multiple */
|
|
for (int i = 0; i < list_length(desired_keys); i++) {
|
|
disKeyInner = lappend(disKeyInner, exact_match_keys_inner[i]);
|
|
disKeyOuter = lappend(disKeyOuter, exact_match_keys_outer[i]);
|
|
}
|
|
pfree_ext(exact_match_keys_outer);
|
|
pfree_ext(exact_match_keys_inner);
|
|
}
|
|
|
|
if (disKeyInner == NIL && disKeyOuter == NIL)
|
|
return;
|
|
|
|
List* inList = NULL;
|
|
List* ouList = NULL;
|
|
|
|
/* If there's a non-skew distribute column, just return it */
|
|
if (*skew_outer <= 1.0 && *skew_inner <= 1.0 && !exact_match) {
|
|
*distribute_keys_inner = lappend(*distribute_keys_inner, copyObject(better_inner_key));
|
|
*distribute_keys_outer = lappend(*distribute_keys_outer, copyObject(better_outer_key));
|
|
} else if (disKeyInner != NULL && disKeyOuter != NULL) {
|
|
/*
|
|
* If all single column is skewed, we'll try to find multiple column
|
|
* as distribute key. For the simplicity, we only consider prefix of
|
|
* all matching columns until we find non-skew combination
|
|
*/
|
|
int len = list_length(disKeyInner);
|
|
int group_num;
|
|
double single_min_skew = min_skew;
|
|
|
|
/* Initialize distribute key search by adding the first one */
|
|
inList = lappend(inList, linitial(disKeyInner));
|
|
ouList = lappend(ouList, linitial(disKeyOuter));
|
|
|
|
/* Continually adding new columns to calculate multiple */
|
|
for (group_num = 2; group_num <= len; group_num++) {
|
|
inList = lappend(inList, list_nth(disKeyInner, group_num - 1));
|
|
ouList = lappend(ouList, list_nth(disKeyOuter, group_num - 1));
|
|
|
|
if (group_num < len && exact_match)
|
|
continue;
|
|
|
|
skew_multiple_inner = get_multiple_by_distkey(root, inList, inner_path->rows);
|
|
skew_multiple_outer = get_multiple_by_distkey(root, ouList, outer_path->rows);
|
|
|
|
/* Find a less skewed column combination, then record it */
|
|
if (skew_multiple_inner * skew_multiple_outer < min_skew) {
|
|
min_skew = skew_multiple_inner * skew_multiple_outer;
|
|
*skew_inner = skew_multiple_inner;
|
|
*skew_outer = skew_multiple_outer;
|
|
}
|
|
/* Found a non-skewed combination, then break */
|
|
if (skew_multiple_inner <= 1.0 && skew_multiple_outer <= 1.0)
|
|
break;
|
|
}
|
|
/* Choose combined distribuite key. we need choose an min skew */
|
|
if (min_skew < single_min_skew || exact_match) {
|
|
*distribute_keys_inner = (List*)copyObject(inList);
|
|
*distribute_keys_outer = (List*)copyObject(ouList);
|
|
} else if (better_inner_key != NULL) { /* Choose single distribuite key */
|
|
*distribute_keys_inner = lappend(*distribute_keys_inner, copyObject(better_inner_key));
|
|
*distribute_keys_outer = lappend(*distribute_keys_outer, copyObject(better_outer_key));
|
|
|
|
} else { /* have not distribuite key */
|
|
*distribute_keys_inner = NIL;
|
|
*distribute_keys_outer = NIL;
|
|
}
|
|
|
|
list_free_ext(inList);
|
|
list_free_ext(ouList);
|
|
}
|
|
|
|
list_free_ext(disKeyInner);
|
|
list_free_ext(disKeyOuter);
|
|
|
|
if (!ng_is_distribute_key_valid(root, *distribute_keys_outer, outer_path->parent->reltargetlist) ||
|
|
!ng_is_distribute_key_valid(root, *distribute_keys_inner, inner_path->parent->reltargetlist)) {
|
|
*distribute_keys_inner = NIL;
|
|
*distribute_keys_outer = NIL;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static List* get_otherside_key(
|
|
PlannerInfo* root, List* rinfo, List* targetlist, RelOptInfo* otherside_rel, double* skew_multiple)
|
|
{
|
|
ListCell* lc1 = NULL;
|
|
ListCell* lc2 = NULL;
|
|
ListCell* lc3 = NULL;
|
|
List* key_list = NULL;
|
|
Node* match_var = NULL;
|
|
ListCell* cell = NULL;
|
|
|
|
foreach (cell, rinfo) {
|
|
EquivalenceClass* oeclass = NULL;
|
|
RestrictInfo* restrictinfo = (RestrictInfo*)lfirst(cell);
|
|
Node* joinkey = NULL;
|
|
|
|
match_var = NULL;
|
|
|
|
if (bms_is_subset(restrictinfo->left_relids, otherside_rel->relids)) {
|
|
oeclass = restrictinfo->left_ec;
|
|
joinkey = join_clause_get_join_key((Node*)restrictinfo->clause, true);
|
|
} else if (bms_is_subset(restrictinfo->right_relids, otherside_rel->relids)) {
|
|
oeclass = restrictinfo->right_ec;
|
|
joinkey = join_clause_get_join_key((Node*)restrictinfo->clause, false);
|
|
}
|
|
|
|
AssertEreport(restrictinfo->orclause == NULL && oeclass != NULL,
|
|
MOD_OPT_JOIN,
|
|
"Restrictinfo should be equal join condition without or clause");
|
|
|
|
foreach (lc1, oeclass->ec_members) {
|
|
EquivalenceMember* em = (EquivalenceMember*)lfirst(lc1);
|
|
Node* nem = (Node*)em->em_expr;
|
|
Oid datatype = exprType(nem);
|
|
List* vars = NIL;
|
|
Relids relIds;
|
|
|
|
if (!OidIsValid(datatype) || !IsTypeDistributable(datatype))
|
|
continue;
|
|
|
|
/*
|
|
* check if the choosen diskey (which might come from an eclass)
|
|
* have compatiable type for hashing with the join key.
|
|
*/
|
|
if (!is_diskey_and_joinkey_compatible(nem, joinkey)) {
|
|
continue;
|
|
}
|
|
|
|
relIds = pull_varnos(nem);
|
|
if (bms_is_empty(relIds) || !bms_is_subset(relIds, otherside_rel->relids)) {
|
|
bms_free_ext(relIds);
|
|
continue;
|
|
}
|
|
bms_free_ext(relIds);
|
|
|
|
/*
|
|
* Check if all vars in sub targetlist
|
|
*
|
|
* For coalesce column in target list, it will presented as a Place Holder,
|
|
* so we will leave it as it is without expand it.
|
|
*/
|
|
vars = pull_var_clause(nem, PVC_REJECT_AGGREGATES, PVC_INCLUDE_PLACEHOLDERS);
|
|
foreach (lc2, vars) {
|
|
Node* node = (Node*)lfirst(lc2);
|
|
foreach (lc3, targetlist) {
|
|
Node* te = (Node*)lfirst(lc3);
|
|
if ((IsA(te, Var) && _equalSimpleVar((Var*)te, node)) || (!IsA(te, Var) && equal(te, node))) {
|
|
break;
|
|
}
|
|
}
|
|
if (lc3 == NULL) /* doesn't find the same in sub target list */
|
|
break;
|
|
}
|
|
list_free_ext(vars);
|
|
if (lc2 != NULL) /* not all vars in sub targetlist */
|
|
continue;
|
|
|
|
match_var = nem;
|
|
break;
|
|
}
|
|
|
|
if (match_var != NULL) {
|
|
key_list = lappend(key_list, copyObject(match_var));
|
|
} else {
|
|
list_free_ext(key_list);
|
|
return NIL;
|
|
}
|
|
}
|
|
|
|
*skew_multiple = get_multiple_by_distkey(root, key_list, otherside_rel->rows);
|
|
|
|
if (!ng_is_distribute_key_valid(root, key_list, targetlist)) {
|
|
list_free_ext(key_list);
|
|
key_list = NIL;
|
|
}
|
|
|
|
return key_list;
|
|
}
|
|
|
|
/* check if subplan is executed on coordinator */
|
|
bool is_subplan_exec_on_coordinator(Path* path)
|
|
{
|
|
if ((path->parent->subplan && is_execute_on_coordinator(path->parent->subplan)))
|
|
return true;
|
|
else if (path->distribution.bms_data_nodeids == NULL)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Check if we shoule use smp code to create a join path.
|
|
*
|
|
* @param[IN] inner: inner join path.
|
|
* @param[IN] outer: outer join path.
|
|
* @return bool: true -- we should use smp code.
|
|
*/
|
|
static bool parallel_enable(Path* inner, Path* outer)
|
|
{
|
|
/*
|
|
* If both sides are not parallelized,
|
|
* then there is no need to parallel the join.
|
|
*/
|
|
if (inner->dop <= 1 && outer->dop <= 1)
|
|
return false;
|
|
|
|
if (u_sess->opt_cxt.query_dop > 1 && IS_STREAM_PLAN)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Check if a Join path can be parallelized.
|
|
*
|
|
* @param[IN] inner: inner join path.
|
|
* @param[IN] outer: outer join path.
|
|
* @return bool: true -- can be parallelized.
|
|
*/
|
|
static bool can_parallel(Path* inner, Path* outer)
|
|
{
|
|
/* Avoid parameterized path to be parallelized. */
|
|
if (inner->param_info != NULL || outer->param_info != NULL)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Set distribute key for each join path in the pathlist,
|
|
* and add it to rel->pathlist.
|
|
*
|
|
* @param[IN] joinpath_list: the join path list wiat to be processed.
|
|
* @param[IN] jointype: join type of outerrel and innerrel.
|
|
* @param[IN] joinrel: the join relatin.
|
|
* @param[IN] joinrel: the planner info.
|
|
* @param[IN] outer_distributekey: distribute key of outerrel.
|
|
* @param[IN] inner_distributekey: distribute key of innerrel.
|
|
* @param[IN] desired_key: matching or superset key in upper level,
|
|
* which is desired in join distribute key.
|
|
* @param[IN] exact_match: note whether in exact mode, and in this
|
|
* mode, we directly return desired_key.
|
|
* @return void
|
|
*/
|
|
static void add_path_list(List* joinpath_list, JoinType jointype, RelOptInfo* joinrel, PlannerInfo* root,
|
|
List* outer_distributekey, List* inner_distributekey, List* desired_key = NIL, bool exact_match = false)
|
|
{
|
|
ListCell* lc = NULL;
|
|
JoinPath* joinpath = NULL;
|
|
foreach (lc, joinpath_list) {
|
|
joinpath = (JoinPath*)lfirst(lc);
|
|
if (jointype != JOIN_FULL) {
|
|
joinpath->path.distribute_keys =
|
|
locate_distribute_key(jointype, outer_distributekey, inner_distributekey, desired_key, exact_match);
|
|
if (joinpath->path.distribute_keys)
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
else
|
|
pfree_ext(joinpath);
|
|
} else
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
}
|
|
|
|
/* Free the list. */
|
|
list_free_ext(joinpath_list);
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Set distribute key for join path with replicate subpath.
|
|
*
|
|
* @param[IN] joinrel: RelOptInfo of join.
|
|
* @param[IN] root: PlannerInfo of join.
|
|
* @param[IN] save_jointype: save join type.
|
|
* @param[IN] joinpath: join path to be processed.
|
|
* @param[IN] replicate_outer/replicate_inner: if the inner/outer path is replicate.
|
|
* @param[IN] redistribute_inner/redistribute_outer: if the inner/outer path can redistribute.
|
|
* @return bool: true -- we can generate redistribute join path.
|
|
*/
|
|
static bool add_replica_join_path(RelOptInfo* joinrel, PlannerInfo* root, JoinType save_jointype, JoinPath* joinpath,
|
|
bool replicate_outer, bool replicate_inner, bool redistribute_inner, bool redistribute_outer)
|
|
{
|
|
Path* inner_path = joinpath->innerjoinpath;
|
|
Path* outer_path = joinpath->outerjoinpath;
|
|
bool can_redistribute = true;
|
|
|
|
if (replicate_outer && replicate_inner) {
|
|
joinpath->path.distribute_keys = NIL;
|
|
} else {
|
|
if (replicate_outer) {
|
|
joinpath->path.distribute_keys = inner_path->distribute_keys;
|
|
} else {
|
|
joinpath->path.distribute_keys = outer_path->distribute_keys;
|
|
}
|
|
}
|
|
|
|
/* Re-set if one side is on CN */
|
|
if (is_subplan_exec_on_coordinator(outer_path) || is_subplan_exec_on_coordinator(inner_path)) {
|
|
joinpath->path.locator_type = LOCATOR_TYPE_REPLICATED;
|
|
joinpath->path.distribute_keys = NIL;
|
|
replicate_outer = true;
|
|
replicate_inner = true;
|
|
}
|
|
|
|
/*
|
|
* Followed cases can choose local plan:
|
|
* 1.Outer is replicate and inner is hash: RHS join or probing side execute on CN, and build side need
|
|
* redistribute; 2.Outer is hash and inner is replicate: LHS join or probing side execute on CN, and build side need
|
|
* redistribute;
|
|
*/
|
|
if (replicate_outer && !replicate_inner) {
|
|
if ((RHS_join(save_jointype) || (is_subplan_exec_on_coordinator(outer_path))) && redistribute_inner) {
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
can_redistribute = false;
|
|
}
|
|
} else if (!replicate_outer && replicate_inner) {
|
|
if ((LHS_join(save_jointype) || (is_subplan_exec_on_coordinator(inner_path))) && redistribute_outer) {
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
can_redistribute = false;
|
|
}
|
|
} else {
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
can_redistribute = false;
|
|
}
|
|
|
|
return can_redistribute;
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Create and add a join path with redistribute.
|
|
* This function is especially designed for smp,
|
|
* which can be used for hashjoin and nestloop.
|
|
*
|
|
* @param[IN] inner_smpDesc: smp info for inner path.
|
|
* @param[IN] outer_smpDesc: smp info for outer path.
|
|
* Other input param please refer to comment of add_join_parallel_path().
|
|
*
|
|
* @return JoinPath*: the JoinPath created by this func.
|
|
*/
|
|
static JoinPath* add_join_redistribute_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype,
|
|
JoinType save_jointype, JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors,
|
|
Path* inner_path, Path* outer_path, ParallelDesc* inner_smpDesc, ParallelDesc* outer_smpDesc, List* restrictlist,
|
|
List* hashclauses, Relids required_outer, double skew_inner, double skew_outer, List* stream_distribute_key_inner,
|
|
List* stream_distribute_key_outer, bool replicate_inner, bool replicate_outer, NodeTag nodetag,
|
|
Distribution* target_distribution, List* inner_pathkeys = NIL, List* outer_pathkeys = NIL)
|
|
{
|
|
Path* stream_path_inner = inner_path;
|
|
Path* stream_path_outer = outer_path;
|
|
JoinPath* joinpath = NULL;
|
|
|
|
/* Set distribute key for local distribute node. */
|
|
if (stream_distribute_key_inner == NULL)
|
|
stream_distribute_key_inner = inner_path->distribute_keys;
|
|
if (stream_distribute_key_outer == NULL)
|
|
stream_distribute_key_outer = outer_path->distribute_keys;
|
|
|
|
/* Confirm the dop of join. */
|
|
AssertEreport(inner_smpDesc->consumerDop == outer_smpDesc->consumerDop,
|
|
MOD_OPT_JOIN,
|
|
"Dop of outer_path and inner_path should be the same");
|
|
int joinDop = inner_smpDesc->consumerDop;
|
|
|
|
/* Add stream path if needed. */
|
|
/* case1: prallel join path. */
|
|
if (joinDop > 1) {
|
|
if (PARALLEL_NONE != inner_smpDesc->distriType)
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key_inner,
|
|
inner_pathkeys,
|
|
true,
|
|
skew_inner,
|
|
target_distribution,
|
|
inner_smpDesc);
|
|
|
|
if (PARALLEL_NONE != outer_smpDesc->distriType)
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key_outer,
|
|
outer_pathkeys,
|
|
false,
|
|
skew_outer,
|
|
target_distribution,
|
|
outer_smpDesc);
|
|
|
|
} else {
|
|
/* case2: do not parallel join path.
|
|
*Check if we need to add extra stream node.
|
|
*/
|
|
if (inner_smpDesc->producerDop > 1 || REMOTE_DISTRIBUTE == inner_smpDesc->distriType)
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key_inner,
|
|
inner_pathkeys,
|
|
true,
|
|
skew_inner,
|
|
target_distribution,
|
|
inner_smpDesc);
|
|
|
|
if (outer_smpDesc->producerDop > 1 || REMOTE_DISTRIBUTE == outer_smpDesc->distriType)
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key_outer,
|
|
outer_pathkeys,
|
|
false,
|
|
skew_outer,
|
|
target_distribution,
|
|
outer_smpDesc);
|
|
}
|
|
|
|
/* Create join path. */
|
|
if (nodetag == T_HashJoin) {
|
|
initial_cost_hashjoin(
|
|
root, workspace, jointype, hashclauses, stream_path_outer, stream_path_inner, sjinfo, semifactors, joinDop);
|
|
|
|
joinpath = (JoinPath*)create_hashjoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
stream_path_outer,
|
|
stream_path_inner,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
joinDop);
|
|
} else {
|
|
initial_cost_nestloop(
|
|
root, workspace, jointype, stream_path_outer, stream_path_inner, sjinfo, semifactors, joinDop);
|
|
|
|
/*
|
|
* When nestloop, hashclauses refer to pathkeys.
|
|
* If the outer path's pathkeys == NIL, then we
|
|
* need to set pathkeys to NIL;
|
|
*/
|
|
if (stream_path_outer->pathkeys == NIL)
|
|
hashclauses = NIL;
|
|
|
|
joinpath = (JoinPath*)create_nestloop_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
stream_path_outer,
|
|
stream_path_inner,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
joinDop);
|
|
}
|
|
|
|
return joinpath;
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Add the logic to handle parallel broadcast situation.
|
|
*
|
|
* @param[IN] need_smpDesc: the smp info in broadcast side.
|
|
* @param[IN] non_smpDesc: the smp info in no-broadcast side.
|
|
* @param[IN] dop: degree of join parallel.
|
|
* Other input param please refer to comment of add_join_parallel_path().
|
|
*
|
|
* @return JoinPath*: the JoinPath created by this func.
|
|
*/
|
|
static void add_hashjoin_broadcast_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype,
|
|
JoinType save_jointype, JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors,
|
|
Path* need_stream_path, Path* non_stream_path, List* restrictlist, Relids required_outer, List* hashclauses,
|
|
bool is_replicate, bool stream_outer, Distribution* target_distribution, ParallelDesc* need_smpDesc,
|
|
ParallelDesc* non_smpDesc, int dop)
|
|
{
|
|
Path* streamed_path = NULL;
|
|
Path* other_side = NULL;
|
|
JoinPath* joinpath = NULL;
|
|
Path* new_outer_path = NULL;
|
|
Path* new_inner_path = NULL;
|
|
|
|
/* target_distribution not likey to be NULL, but let's play safe */
|
|
if (NULL == target_distribution) {
|
|
target_distribution = ng_get_default_computing_group_distribution();
|
|
}
|
|
|
|
/* If parallel, add parallel info to the path. */
|
|
streamed_path = stream_side_path(root,
|
|
need_stream_path,
|
|
save_jointype,
|
|
is_replicate,
|
|
STREAM_BROADCAST,
|
|
NIL,
|
|
NIL,
|
|
!stream_outer,
|
|
1.0,
|
|
target_distribution,
|
|
need_smpDesc);
|
|
|
|
/* non-broadcast side also needs shuffle if node group is un-matched */
|
|
non_stream_path = ng_stream_non_broadcast_side_for_join(
|
|
root, non_stream_path, save_jointype, NIL, is_replicate, stream_outer, target_distribution);
|
|
if (NULL == non_stream_path) {
|
|
/* non-broadcast side can not shuffle */
|
|
return;
|
|
}
|
|
|
|
if (NULL == non_smpDesc) {
|
|
other_side = non_stream_path;
|
|
} else {
|
|
if (PARALLEL_NONE != non_smpDesc->distriType) {
|
|
other_side = stream_side_path(root,
|
|
non_stream_path,
|
|
save_jointype,
|
|
is_replicate,
|
|
STREAM_REDISTRIBUTE,
|
|
NIL,
|
|
NIL,
|
|
stream_outer,
|
|
1.0,
|
|
target_distribution,
|
|
non_smpDesc);
|
|
} else
|
|
other_side = non_stream_path;
|
|
}
|
|
|
|
/* Confirm the inner path and outer path. */
|
|
new_outer_path = stream_outer ? streamed_path : other_side;
|
|
new_inner_path = stream_outer ? other_side : streamed_path;
|
|
|
|
initial_cost_hashjoin(
|
|
root, workspace, jointype, hashclauses, new_outer_path, new_inner_path, sjinfo, semifactors, dop);
|
|
|
|
joinpath = (JoinPath*)create_hashjoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
new_outer_path,
|
|
new_inner_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
dop);
|
|
|
|
joinpath->path.distribute_keys = non_stream_path->distribute_keys;
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Add parallel info for join with replicate table.
|
|
*
|
|
* @param[IN] is_replicate: is the path a replicate one.
|
|
* @param[IN] path: the path needed to be processed.
|
|
* @param[IN] smpDesc: smp info for this path.
|
|
* @param[IN] stream: streamType -- broadcast/redistribute/none
|
|
*
|
|
* @return void
|
|
*/
|
|
static void set_replicate_parallel_info(bool is_replicate, Path* path, ParallelDesc* smpDesc, StreamType stream)
|
|
{
|
|
if (is_replicate) {
|
|
/*
|
|
* Generally we do not parallel replicate path,
|
|
* unless we want to make the join path parallel.
|
|
*/
|
|
if (STREAM_REDISTRIBUTE == stream) {
|
|
smpDesc->distriType = REMOTE_SPLIT_DISTRIBUTE;
|
|
} else {
|
|
smpDesc->distriType = LOCAL_BROADCAST;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Create a parallel and unparallel join path when enable smp.
|
|
* And add them to the path list.
|
|
* This function is designed for hashjoin and nestloop.
|
|
*
|
|
* @param[IN] inner_stream: the inner side stream type.
|
|
* @param[IN] outer_stream: the outer side stream type.
|
|
* @param[IN] root: the plannerInfo for this join.
|
|
* @param[IN] joinrel: the join relation
|
|
* @param[IN] sjinfo: extra info about the join for selectivity estimation
|
|
* @param[IN] semifactors: contains valid data if jointype is SEMI or ANTI.
|
|
* @param[IN] inner_path: the inner subpath for join.
|
|
* @param[IN] outer_path: the outer subpath for join.
|
|
* @param[IN] skew_inner: data skew for inner path.
|
|
* @param[IN] skew_outer: data skew for outer path.
|
|
* @param[IN] jointype: join type.
|
|
* @param[IN] save_jointype: save join type.
|
|
* @param[IN] required_outer: the set of required outer rels.
|
|
* @param[IN] workspace: workspace to record join cost.
|
|
* @param[IN] replicate_inner: is inner path replicate or not.
|
|
* @param[IN] replicate_outer: is outer path replicate or not.
|
|
* @param[IN] stream_distribute_key_inner: distributekey for inner stream.
|
|
* @param[IN] stream_distribute_key_outer: distributekey for outer stream.
|
|
* @param[IN] hashclauses: the RestrictInfo nodes to use as hash clauses.
|
|
* @param[IN] restrictlist: the RestrictInfo nodes to apply at the join.
|
|
* @param[IN] nodetag: Nestloop or HashJoin.
|
|
* @param[IN] inner_pathkeys: inner path sort key.
|
|
*
|
|
* @param[OUT] outer_pathkeys: outer path sort key.
|
|
*
|
|
* @return void
|
|
*/
|
|
static List* add_join_parallel_path(StreamType inner_stream, StreamType outer_stream, PlannerInfo* root,
|
|
RelOptInfo* joinrel, SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors, Path* inner_path, Path* outer_path,
|
|
double skew_inner, double skew_outer, JoinType jointype, JoinType save_jointype, Relids required_outer,
|
|
JoinCostWorkspace* workspace, bool replicate_inner, bool replicate_outer, List* stream_distribute_key_inner,
|
|
List* stream_distribute_key_outer, List* hashclauses, List* restrictlist, NodeTag nodetag,
|
|
Distribution* target_distribution, List* inner_pathkeys = NIL, List* outer_pathkeys = NIL)
|
|
{
|
|
/*
|
|
* When the user turn on SMP, we need to add parallel path
|
|
* to the alternative path list.
|
|
* That means we need to handle two main situations:
|
|
* 1. Create a parallel join path whether the subpath
|
|
* is parallel or not.
|
|
* 2. Create a unparallel join path especially when
|
|
* the subpath is parallel. If the subpath is
|
|
* unparalleled, then we just treat it as serial
|
|
* path, otherwise we need to do additional handling.
|
|
*/
|
|
AssertEreport(
|
|
nodetag == T_HashJoin || nodetag == T_NestLoop, MOD_OPT_JOIN, "Join method should be hashjoin or nestloop");
|
|
List* joinpath_list = NIL;
|
|
JoinPath* joinpath = NULL;
|
|
|
|
/* 1. Create parallel join path. */
|
|
/* create two smp desc. */
|
|
ParallelDesc* inner_smpDesc = create_smpDesc(u_sess->opt_cxt.query_dop, inner_path->dop, PARALLEL_NONE);
|
|
ParallelDesc* outer_smpDesc = create_smpDesc(u_sess->opt_cxt.query_dop, outer_path->dop, PARALLEL_NONE);
|
|
|
|
bool inner_can_local_distribute =
|
|
check_dsitribute_key_in_targetlist(root, inner_path->distribute_keys, inner_path->parent->reltargetlist);
|
|
bool outer_can_local_distribute =
|
|
check_dsitribute_key_in_targetlist(root, outer_path->distribute_keys, outer_path->parent->reltargetlist);
|
|
|
|
/*
|
|
* If we already have redistribute or local redistribute
|
|
* in the subquery path, then there is no need to add
|
|
* new local redistribute for parallelism.
|
|
*/
|
|
bool inner_need_local_distribute = true;
|
|
bool outer_need_local_distribute = true;
|
|
|
|
Path* in_tmp = inner_path;
|
|
Path* out_tmp = outer_path;
|
|
|
|
/* This kind of unique path is dummy path, skip it. */
|
|
if (T_Unique == inner_path->pathtype && UNIQUE_PATH_NOOP == ((UniquePath*)inner_path)->umethod)
|
|
in_tmp = ((UniquePath*)inner_path)->subpath;
|
|
if (T_Unique == outer_path->pathtype && UNIQUE_PATH_NOOP == ((UniquePath*)outer_path)->umethod)
|
|
out_tmp = ((UniquePath*)outer_path)->subpath;
|
|
|
|
/*
|
|
* Check the subqueryscan path to avoid additional redistribution
|
|
* incase that subplan has already local distributed.
|
|
*/
|
|
if (in_tmp->pathtype == T_SubqueryScan) {
|
|
Plan* subplan = in_tmp->parent->subplan;
|
|
inner_need_local_distribute = is_local_redistribute_needed(subplan);
|
|
}
|
|
|
|
if (out_tmp->pathtype == T_SubqueryScan) {
|
|
Plan* subplan = out_tmp->parent->subplan;
|
|
outer_need_local_distribute = is_local_redistribute_needed(subplan);
|
|
}
|
|
|
|
/*
|
|
* Create parallel join path.
|
|
*/
|
|
/* can set pathkey to NIL. */
|
|
if (can_parallel(inner_path, outer_path)) {
|
|
/* Scenario 1: local join. */
|
|
if (STREAM_NONE == inner_stream && STREAM_NONE == outer_stream) {
|
|
if (replicate_inner || replicate_outer) {
|
|
/* Set parallel info. */
|
|
set_replicate_parallel_info(replicate_inner, inner_path, inner_smpDesc, inner_stream);
|
|
set_replicate_parallel_info(replicate_outer, outer_path, outer_smpDesc, outer_stream);
|
|
|
|
joinpath = (JoinPath*)add_join_redistribute_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
inner_smpDesc,
|
|
outer_smpDesc,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
skew_inner,
|
|
skew_outer,
|
|
NIL,
|
|
NIL,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
nodetag,
|
|
target_distribution,
|
|
NIL,
|
|
NIL);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
/* There is 3 possible parallel path. */
|
|
/* case 1:local broadcast inner */
|
|
if (outer_path->pathtype != T_Unique && inner_path->pathtype != T_Unique &&
|
|
can_broadcast_inner(jointype, save_jointype, replicate_outer, NIL, NIL)) {
|
|
inner_smpDesc->distriType = LOCAL_BROADCAST;
|
|
if (outer_smpDesc->producerDop <= 1)
|
|
outer_smpDesc->distriType = LOCAL_ROUNDROBIN;
|
|
|
|
joinpath = (JoinPath*)add_join_redistribute_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
inner_smpDesc,
|
|
outer_smpDesc,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
skew_inner,
|
|
skew_outer,
|
|
stream_distribute_key_inner,
|
|
stream_distribute_key_outer,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
nodetag,
|
|
target_distribution,
|
|
NIL,
|
|
NIL);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
}
|
|
|
|
/* case 2:local broadcast outer */
|
|
if (outer_path->pathtype != T_Unique && inner_path->pathtype != T_Unique &&
|
|
can_broadcast_outer(jointype, save_jointype, replicate_inner, NIL, NIL)) {
|
|
ParallelDesc* inner_smpDesc1 =
|
|
create_smpDesc(u_sess->opt_cxt.query_dop, inner_path->dop, PARALLEL_NONE);
|
|
ParallelDesc* outer_smpDesc1 =
|
|
create_smpDesc(u_sess->opt_cxt.query_dop, outer_path->dop, PARALLEL_NONE);
|
|
|
|
outer_smpDesc1->distriType = LOCAL_BROADCAST;
|
|
if (inner_smpDesc1->producerDop <= 1)
|
|
inner_smpDesc1->distriType = LOCAL_ROUNDROBIN;
|
|
|
|
joinpath = (JoinPath*)add_join_redistribute_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
inner_smpDesc1,
|
|
outer_smpDesc1,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
skew_inner,
|
|
skew_outer,
|
|
stream_distribute_key_inner,
|
|
stream_distribute_key_outer,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
nodetag,
|
|
target_distribution,
|
|
NIL,
|
|
NIL);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
}
|
|
|
|
/* Check if we can use local redistribute. */
|
|
if (inner_can_local_distribute && outer_can_local_distribute) {
|
|
/* case 3:local distribute inner and outer */
|
|
ParallelDesc* inner_smpDesc2 =
|
|
create_smpDesc(u_sess->opt_cxt.query_dop, inner_path->dop, PARALLEL_NONE);
|
|
ParallelDesc* outer_smpDesc2 =
|
|
create_smpDesc(u_sess->opt_cxt.query_dop, outer_path->dop, PARALLEL_NONE);
|
|
|
|
if (inner_need_local_distribute)
|
|
inner_smpDesc2->distriType = LOCAL_DISTRIBUTE;
|
|
if (outer_need_local_distribute)
|
|
outer_smpDesc2->distriType = LOCAL_DISTRIBUTE;
|
|
|
|
joinpath = (JoinPath*)add_join_redistribute_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
inner_smpDesc2,
|
|
outer_smpDesc2,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
skew_inner,
|
|
skew_outer,
|
|
stream_distribute_key_inner,
|
|
stream_distribute_key_outer,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
nodetag,
|
|
target_distribution,
|
|
NIL,
|
|
NIL);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
}
|
|
}
|
|
} else if (STREAM_REDISTRIBUTE == inner_stream || STREAM_REDISTRIBUTE == outer_stream) {
|
|
/* Scenario 2: join with redistribute. */
|
|
if (STREAM_REDISTRIBUTE == inner_stream && STREAM_REDISTRIBUTE == outer_stream) {
|
|
inner_smpDesc->distriType = REMOTE_SPLIT_DISTRIBUTE;
|
|
outer_smpDesc->distriType = REMOTE_SPLIT_DISTRIBUTE;
|
|
} else if (STREAM_REDISTRIBUTE == inner_stream && STREAM_REDISTRIBUTE != outer_stream) {
|
|
inner_smpDesc->distriType = REMOTE_SPLIT_DISTRIBUTE;
|
|
if (outer_need_local_distribute)
|
|
outer_smpDesc->distriType = LOCAL_DISTRIBUTE;
|
|
} else if (STREAM_REDISTRIBUTE != inner_stream && STREAM_REDISTRIBUTE == outer_stream) {
|
|
if (inner_need_local_distribute)
|
|
inner_smpDesc->distriType = LOCAL_DISTRIBUTE;
|
|
outer_smpDesc->distriType = REMOTE_SPLIT_DISTRIBUTE;
|
|
}
|
|
|
|
if ((LOCAL_DISTRIBUTE != inner_smpDesc->distriType || inner_can_local_distribute) &&
|
|
(LOCAL_DISTRIBUTE != outer_smpDesc->distriType || outer_can_local_distribute)) {
|
|
/* Set parallel info for replicate table. */
|
|
if (replicate_inner)
|
|
set_replicate_parallel_info(replicate_inner, inner_path, inner_smpDesc, inner_stream);
|
|
if (replicate_outer)
|
|
set_replicate_parallel_info(replicate_outer, outer_path, outer_smpDesc, outer_stream);
|
|
|
|
joinpath = (JoinPath*)add_join_redistribute_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
inner_smpDesc,
|
|
outer_smpDesc,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
skew_inner,
|
|
skew_outer,
|
|
stream_distribute_key_inner,
|
|
stream_distribute_key_outer,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
nodetag,
|
|
target_distribution,
|
|
NIL,
|
|
NIL);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
}
|
|
} else {
|
|
/* Scenario 3: join with broadcast. */
|
|
/* Do not support replicate table with broadcast. */
|
|
if (!replicate_inner && !replicate_outer) {
|
|
if (STREAM_BROADCAST == inner_stream) {
|
|
inner_smpDesc->distriType = REMOTE_SPLIT_BROADCAST;
|
|
if (outer_smpDesc->producerDop <= 1)
|
|
outer_smpDesc->distriType = LOCAL_ROUNDROBIN;
|
|
|
|
if (nodetag == T_HashJoin)
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution,
|
|
inner_smpDesc,
|
|
outer_smpDesc,
|
|
u_sess->opt_cxt.query_dop);
|
|
else
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
NIL,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution,
|
|
inner_smpDesc,
|
|
outer_smpDesc,
|
|
u_sess->opt_cxt.query_dop);
|
|
} else if (STREAM_BROADCAST == outer_stream) {
|
|
outer_smpDesc->distriType = REMOTE_SPLIT_BROADCAST;
|
|
if (inner_smpDesc->producerDop <= 1)
|
|
inner_smpDesc->distriType = LOCAL_ROUNDROBIN;
|
|
|
|
if (nodetag == T_HashJoin)
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution,
|
|
outer_smpDesc,
|
|
inner_smpDesc,
|
|
u_sess->opt_cxt.query_dop);
|
|
else
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
NIL,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution,
|
|
outer_smpDesc,
|
|
inner_smpDesc,
|
|
u_sess->opt_cxt.query_dop);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 2. Add a unparallel join path. */
|
|
/* Create two smp desc. */
|
|
ParallelDesc* inner_unpara_smpDesc = create_smpDesc(1, inner_path->dop, PARALLEL_NONE);
|
|
ParallelDesc* outer_unpara_smpDesc = create_smpDesc(1, outer_path->dop, PARALLEL_NONE);
|
|
|
|
if (STREAM_BROADCAST != inner_stream && STREAM_BROADCAST != outer_stream) {
|
|
if (STREAM_NONE == inner_stream && STREAM_NONE == outer_stream) {
|
|
inner_unpara_smpDesc->distriType = LOCAL_ROUNDROBIN;
|
|
outer_unpara_smpDesc->distriType = LOCAL_ROUNDROBIN;
|
|
} else if (STREAM_REDISTRIBUTE == inner_stream && STREAM_REDISTRIBUTE == outer_stream) {
|
|
inner_unpara_smpDesc->distriType = REMOTE_DISTRIBUTE;
|
|
outer_unpara_smpDesc->distriType = REMOTE_DISTRIBUTE;
|
|
} else if (STREAM_REDISTRIBUTE == inner_stream && STREAM_REDISTRIBUTE != outer_stream) {
|
|
inner_unpara_smpDesc->distriType = REMOTE_DISTRIBUTE;
|
|
outer_unpara_smpDesc->distriType = LOCAL_ROUNDROBIN;
|
|
} else if (STREAM_REDISTRIBUTE != inner_stream && STREAM_REDISTRIBUTE == outer_stream) {
|
|
inner_unpara_smpDesc->distriType = LOCAL_ROUNDROBIN;
|
|
outer_unpara_smpDesc->distriType = REMOTE_DISTRIBUTE;
|
|
}
|
|
joinpath = add_join_redistribute_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
inner_unpara_smpDesc,
|
|
outer_unpara_smpDesc,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
skew_inner,
|
|
skew_outer,
|
|
stream_distribute_key_inner,
|
|
stream_distribute_key_outer,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
nodetag,
|
|
target_distribution,
|
|
NIL,
|
|
NIL);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
if (STREAM_BROADCAST == inner_stream) {
|
|
inner_unpara_smpDesc->distriType = REMOTE_BROADCAST;
|
|
if (outer_unpara_smpDesc->producerDop > 1) {
|
|
outer_unpara_smpDesc->distriType = LOCAL_ROUNDROBIN;
|
|
/* Do not add local gather path above replicate table. */
|
|
if (is_replicated_path(outer_path))
|
|
return joinpath_list;
|
|
}
|
|
if (nodetag == T_HashJoin)
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution,
|
|
inner_unpara_smpDesc,
|
|
outer_unpara_smpDesc);
|
|
else
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
NIL,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution,
|
|
inner_unpara_smpDesc,
|
|
outer_unpara_smpDesc,
|
|
1);
|
|
} else if (STREAM_BROADCAST == outer_stream) {
|
|
if (inner_unpara_smpDesc->producerDop > 1) {
|
|
inner_unpara_smpDesc->distriType = LOCAL_ROUNDROBIN;
|
|
if (is_replicated_path(inner_path))
|
|
return joinpath_list;
|
|
}
|
|
outer_unpara_smpDesc->distriType = REMOTE_BROADCAST;
|
|
|
|
if (nodetag == T_HashJoin)
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution,
|
|
outer_unpara_smpDesc,
|
|
inner_unpara_smpDesc);
|
|
else
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrictlist,
|
|
hashclauses,
|
|
required_outer,
|
|
NIL,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution,
|
|
outer_unpara_smpDesc,
|
|
inner_unpara_smpDesc,
|
|
1);
|
|
}
|
|
}
|
|
|
|
return joinpath_list;
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Add hashjoin path in stream mode.
|
|
*
|
|
* The input param please refer to comment of add_join_parallel_path().
|
|
*
|
|
* @return JoinPath*: the JoinPath created by this func.
|
|
*/
|
|
void add_hashjoin_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype, JoinType save_jointype,
|
|
JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors, Path* outer_path,
|
|
Path* inner_path, List* restrictlist, Relids required_outer, List* hashclauses, Distribution* target_distribution)
|
|
{
|
|
bool redistribute_inner = true;
|
|
bool redistribute_outer = true;
|
|
bool replicate_inner = false;
|
|
bool replicate_outer = false;
|
|
List* distribute_keys_inner = NIL;
|
|
List* distribute_keys_outer = NIL;
|
|
List* joinclauses = NIL;
|
|
RelOptInfo* outerrel = NULL;
|
|
RelOptInfo* innerrel = NULL;
|
|
JoinPath* joinpath = NULL;
|
|
List* joinpath_list = NIL;
|
|
ListCell* lc = NULL;
|
|
|
|
List* rrinfo_inner = NULL;
|
|
List* rrinfo_outer = NULL;
|
|
List* stream_distribute_key = NIL;
|
|
|
|
NodeTag nodetag = T_HashJoin;
|
|
|
|
outerrel = outer_path->parent;
|
|
innerrel = inner_path->parent;
|
|
joinclauses = hashclauses;
|
|
|
|
distribute_keys_inner = inner_path->distribute_keys;
|
|
distribute_keys_outer = outer_path->distribute_keys;
|
|
|
|
if (is_replicated_path(outer_path))
|
|
replicate_outer = true;
|
|
|
|
if (is_replicated_path(inner_path))
|
|
replicate_inner = true;
|
|
|
|
/* joinclauses of hashjoin should be Non-null. */
|
|
AssertEreport(joinclauses != NIL, MOD_OPT_JOIN, "Joinclauses of hashjoin should be Non-null");
|
|
|
|
/*
|
|
* Wheather inner or outer need to be redistributed base on their distribute key and join clauses
|
|
* TRUE means need to be redistributed,
|
|
* FALSE means do not need to be redistributed
|
|
*/
|
|
redistribute_inner = is_distribute_need_on_joinclauses(
|
|
root, inner_path->distribute_keys, joinclauses, innerrel, outerrel, &rrinfo_inner);
|
|
redistribute_outer = is_distribute_need_on_joinclauses(
|
|
root, outer_path->distribute_keys, joinclauses, outerrel, innerrel, &rrinfo_outer);
|
|
|
|
/*
|
|
* Check node group distribution
|
|
* If path's distribution is different from target_distribution (computing node group), shuffle is needed
|
|
*/
|
|
redistribute_inner = redistribute_inner || ng_is_shuffle_needed(root, inner_path, target_distribution);
|
|
redistribute_outer = redistribute_outer || ng_is_shuffle_needed(root, outer_path, target_distribution);
|
|
|
|
/*
|
|
* If either side is replicated, join locally.
|
|
*/
|
|
if (replicate_outer || replicate_inner) {
|
|
/*
|
|
* Check if we need do further redistribution even with two replicate table
|
|
* and shuffle them to same computing node group.
|
|
*/
|
|
Path* outer_path_t = outer_path;
|
|
Path* inner_path_t = inner_path;
|
|
ng_stream_side_paths_for_replicate(
|
|
root, &outer_path_t, &inner_path_t, save_jointype, false, target_distribution);
|
|
|
|
if (NULL != outer_path_t && NULL != inner_path_t) {
|
|
/*
|
|
* Do not parallel join when both sides are replicate table.
|
|
*/
|
|
if (!parallel_enable(inner_path_t, outer_path_t)) {
|
|
if (outer_path != outer_path_t || inner_path != inner_path_t) {
|
|
initial_cost_hashjoin(
|
|
root, workspace, jointype, hashclauses, outer_path_t, inner_path_t, sjinfo, semifactors, 1);
|
|
}
|
|
|
|
joinpath = (JoinPath*)create_hashjoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path_t,
|
|
inner_path_t,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_NONE,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path_t,
|
|
outer_path_t,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
|
|
bool can_redistribute = true;
|
|
foreach (lc, joinpath_list) {
|
|
joinpath = (JoinPath*)lfirst(lc);
|
|
can_redistribute = can_redistribute && add_replica_join_path(joinrel,
|
|
root,
|
|
save_jointype,
|
|
joinpath,
|
|
replicate_outer,
|
|
replicate_inner,
|
|
redistribute_inner,
|
|
redistribute_outer);
|
|
}
|
|
|
|
/* Can not create redistribute path anymore. */
|
|
if (!can_redistribute)
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Four scenarios
|
|
*/
|
|
Path* stream_path_inner = NULL;
|
|
Path* stream_path_outer = NULL;
|
|
if (redistribute_inner && !redistribute_outer) {
|
|
/*
|
|
* Three paths, redistribute inner or broadcast outer or broadcast inner(if redistribute inner is unavailable)
|
|
*/
|
|
double skew_stream = 0.0;
|
|
joinpath_list = NIL;
|
|
|
|
/* For redistribute, the distribute key should be in the targetlist of joinrel */
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_outer, innerrel->reltargetlist, inner_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
NIL,
|
|
true,
|
|
skew_stream,
|
|
target_distribution);
|
|
initial_cost_hashjoin(
|
|
root, workspace, jointype, hashclauses, outer_path, stream_path_inner, sjinfo, semifactors, 1);
|
|
|
|
joinpath = (JoinPath*)create_hashjoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
(Path*)stream_path_inner,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_REDISTRIBUTE,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
skew_stream,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
stream_distribute_key,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
|
|
add_path_list(joinpath_list, jointype, joinrel, root, distribute_keys_outer, stream_distribute_key);
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_BROADCAST,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
|
|
if (can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_NONE,
|
|
STREAM_BROADCAST,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
} else if (!redistribute_inner && redistribute_outer) {
|
|
/*
|
|
* Three paths, broadcast inner or redistribute outer or broadcast outer(if redistribute outer is unavailable)
|
|
*/
|
|
{
|
|
double skew_stream = 0.0;
|
|
joinpath_list = NIL;
|
|
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_inner, outerrel->reltargetlist, outer_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
NIL,
|
|
false,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_hashjoin(
|
|
root, workspace, jointype, hashclauses, stream_path_outer, inner_path, sjinfo, semifactors, 1);
|
|
|
|
joinpath = (JoinPath*)create_hashjoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
(Path*)stream_path_outer,
|
|
inner_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_NONE,
|
|
STREAM_REDISTRIBUTE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
skew_stream,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
stream_distribute_key,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
|
|
add_path_list(joinpath_list, jointype, joinrel, root, stream_distribute_key, distribute_keys_inner);
|
|
}
|
|
}
|
|
|
|
if (can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_BROADCAST,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_NONE,
|
|
STREAM_BROADCAST,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
|
|
} else if (redistribute_inner && redistribute_outer) {
|
|
int i = joinrel->rel_dis_keys.matching_keys != NIL ? -1 : 0; /* loop start */
|
|
int key_num = list_length(joinrel->rel_dis_keys.superset_keys);
|
|
List* old_distribute_keys = NIL;
|
|
bool choose_optimal = false;
|
|
|
|
/*
|
|
* Three paths, broadcast inner or broadcast outer or redistribute inner and outer
|
|
*/
|
|
/*
|
|
* For redistribute path, we check all the matching key and superset keys
|
|
* to be distribute keys if possible. We check with the following sequence:
|
|
* (1) matching key; (2) superset key; (3) optimal key. We use variable i
|
|
* to track all process, with (1) i = -1; (2) i = 0 to key_num -1;
|
|
* (3) i = key_num. During whole process, we skip if distribute key is already
|
|
* used before. Also, if (3) is found in (1) and (2), we just skip (3).
|
|
*/
|
|
for (; i <= key_num; i++) {
|
|
List* redistribute_keys_inner = NIL;
|
|
List* redistribute_keys_outer = NIL;
|
|
double skew_outer = 0.0;
|
|
double skew_inner = 0.0;
|
|
List* desired_keys = NIL;
|
|
joinpath_list = NIL;
|
|
|
|
if (i == -1)
|
|
desired_keys = joinrel->rel_dis_keys.matching_keys;
|
|
else if (i < key_num)
|
|
desired_keys = (List*)list_nth(joinrel->rel_dis_keys.superset_keys, i);
|
|
|
|
if (i == key_num && choose_optimal)
|
|
continue;
|
|
|
|
/* Determine which clause both sides redistribute on */
|
|
get_distribute_keys(root,
|
|
joinclauses,
|
|
outer_path,
|
|
inner_path,
|
|
&skew_outer,
|
|
&skew_inner,
|
|
&redistribute_keys_outer,
|
|
&redistribute_keys_inner,
|
|
desired_keys,
|
|
(i == -1));
|
|
|
|
if (redistribute_keys_inner != NIL && redistribute_keys_outer != NIL) {
|
|
if (skew_outer <= 1.0 && skew_inner <= 1.0)
|
|
choose_optimal = true;
|
|
|
|
if (list_member(old_distribute_keys, redistribute_keys_outer))
|
|
continue;
|
|
else
|
|
old_distribute_keys = lappend(old_distribute_keys, redistribute_keys_outer);
|
|
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
redistribute_keys_inner,
|
|
NIL,
|
|
true,
|
|
skew_inner,
|
|
target_distribution);
|
|
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
redistribute_keys_outer,
|
|
NIL,
|
|
false,
|
|
skew_outer,
|
|
target_distribution);
|
|
|
|
initial_cost_hashjoin(root,
|
|
workspace,
|
|
jointype,
|
|
hashclauses,
|
|
stream_path_outer,
|
|
stream_path_inner,
|
|
sjinfo,
|
|
semifactors,
|
|
1);
|
|
|
|
joinpath = (JoinPath*)create_hashjoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
(Path*)stream_path_outer,
|
|
(Path*)stream_path_inner,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_REDISTRIBUTE,
|
|
STREAM_REDISTRIBUTE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
skew_inner,
|
|
skew_outer,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
redistribute_keys_inner,
|
|
redistribute_keys_outer,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
add_path_list(joinpath_list,
|
|
jointype,
|
|
joinrel,
|
|
root,
|
|
redistribute_keys_outer,
|
|
redistribute_keys_inner,
|
|
desired_keys,
|
|
(i == -1));
|
|
}
|
|
}
|
|
list_free_ext(old_distribute_keys);
|
|
|
|
if (can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_BROADCAST,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
if (can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_NONE,
|
|
STREAM_BROADCAST,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
} else if (!redistribute_inner && !redistribute_outer) {
|
|
/*
|
|
* if redistribute on different join key, still need to redistribute either one.
|
|
*/
|
|
if (rrinfo_inner != NULL && rrinfo_outer != NULL && !equal(rrinfo_inner, rrinfo_outer)) {
|
|
/*
|
|
* The distribute_keys_inner should be identical to innerpath->distribute_keys here.
|
|
* The distribute_keys_outer should be identical to outerpath->distribute_keys here.
|
|
*/
|
|
{
|
|
double skew_stream = 0.0;
|
|
joinpath_list = NIL;
|
|
|
|
/* For redistribute, the distribute key should be in the targetlist of joinrel */
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_outer, innerrel->reltargetlist, inner_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
NIL,
|
|
true,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_hashjoin(root,
|
|
workspace,
|
|
jointype,
|
|
hashclauses,
|
|
outer_path,
|
|
stream_path_inner,
|
|
sjinfo,
|
|
semifactors,
|
|
1);
|
|
|
|
joinpath = (JoinPath*)create_hashjoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
(Path*)stream_path_inner,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_REDISTRIBUTE,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
skew_stream,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
stream_distribute_key,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
|
|
add_path_list(joinpath_list, jointype, joinrel, root, distribute_keys_outer, stream_distribute_key);
|
|
}
|
|
}
|
|
|
|
{
|
|
double skew_stream = 0.0;
|
|
joinpath_list = NIL;
|
|
|
|
/* For redistribute, the distribute key should be in the targetlist of joinrel */
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_inner, outerrel->reltargetlist, outer_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
NIL,
|
|
false,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_hashjoin(root,
|
|
workspace,
|
|
jointype,
|
|
hashclauses,
|
|
stream_path_outer,
|
|
inner_path,
|
|
sjinfo,
|
|
semifactors,
|
|
1);
|
|
|
|
joinpath = (JoinPath*)create_hashjoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
(Path*)stream_path_outer,
|
|
inner_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_NONE,
|
|
STREAM_REDISTRIBUTE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
skew_stream,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
stream_distribute_key,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
|
|
add_path_list(joinpath_list, jointype, joinrel, root, stream_distribute_key, distribute_keys_inner);
|
|
}
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_BROADCAST,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_hashjoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_NONE,
|
|
STREAM_BROADCAST,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
} else {
|
|
/* Join local */
|
|
joinpath_list = NIL;
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
joinpath = (JoinPath*)create_hashjoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrictlist,
|
|
required_outer,
|
|
hashclauses);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_NONE,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
hashclauses,
|
|
restrictlist,
|
|
nodetag,
|
|
target_distribution);
|
|
}
|
|
add_path_list(joinpath_list, jointype, joinrel, root, distribute_keys_outer, distribute_keys_inner);
|
|
}
|
|
}
|
|
|
|
list_free_ext(rrinfo_inner);
|
|
list_free_ext(rrinfo_outer);
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Add nestloop join path with broadcast.
|
|
*
|
|
* @param[IN] need_smpDesc: the smp info in broadcast side.
|
|
* @param[IN] non_smpDesc: the smp info in no-broadcast side.
|
|
* @param[IN] dop: degree of join parallel.
|
|
* Other input param please refer to comment of add_join_parallel_path().
|
|
*
|
|
* @return JoinPath*: the JoinPath created by this func.
|
|
*/
|
|
static void add_nestloop_broadcast_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype,
|
|
JoinType save_jointype, JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors,
|
|
Path* need_stream_path, Path* non_stream_path, List* restrict_clauses, List* pathkeys, Relids required_outer,
|
|
List* stream_pathkeys, bool is_replicate, bool stream_outer, Distribution* target_distribution,
|
|
ParallelDesc* need_smpDesc, ParallelDesc* non_smpDesc, int dop)
|
|
{
|
|
Path* streamed_path = NULL;
|
|
Path* other_side = NULL;
|
|
JoinPath* joinpath = NULL;
|
|
Path* new_outer_path = NULL;
|
|
Path* new_inner_path = NULL;
|
|
|
|
/* target_distribution would be NULL in SMP path, set it to default group of current mode */
|
|
if (NULL == target_distribution) {
|
|
target_distribution = ng_get_default_computing_group_distribution();
|
|
}
|
|
|
|
/* If parallel, add parallel info to the path. */
|
|
streamed_path = stream_side_path(root,
|
|
need_stream_path,
|
|
save_jointype,
|
|
is_replicate,
|
|
STREAM_BROADCAST,
|
|
NIL,
|
|
stream_pathkeys,
|
|
!stream_outer,
|
|
1.0,
|
|
target_distribution,
|
|
need_smpDesc);
|
|
|
|
/* non-broadcast side also needs shuffle if node group is un-matched */
|
|
non_stream_path = ng_stream_non_broadcast_side_for_join(
|
|
root, non_stream_path, save_jointype, NIL, is_replicate, stream_outer, target_distribution);
|
|
if (NULL == non_stream_path) {
|
|
/* non-broadcast side can not shuffle */
|
|
return;
|
|
}
|
|
|
|
if (NULL != non_smpDesc && PARALLEL_NONE != non_smpDesc->distriType) {
|
|
other_side = stream_side_path(root,
|
|
non_stream_path,
|
|
save_jointype,
|
|
is_replicate,
|
|
STREAM_REDISTRIBUTE,
|
|
NIL,
|
|
NIL,
|
|
stream_outer,
|
|
1.0,
|
|
target_distribution,
|
|
non_smpDesc);
|
|
} else {
|
|
other_side = non_stream_path;
|
|
}
|
|
|
|
new_outer_path = stream_outer ? streamed_path : other_side;
|
|
new_inner_path = stream_outer ? other_side : streamed_path;
|
|
|
|
initial_cost_nestloop(root, workspace, jointype, new_outer_path, new_inner_path, sjinfo, semifactors, dop);
|
|
|
|
joinpath = (JoinPath*)create_nestloop_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
new_outer_path,
|
|
new_inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
dop);
|
|
|
|
joinpath->path.distribute_keys = non_stream_path->distribute_keys;
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
}
|
|
|
|
/*
|
|
* @Description:
|
|
* Add nestloop join path in stream mode.
|
|
*
|
|
* The input param please refer to comment of add_join_parallel_path().
|
|
*
|
|
* @return JoinPath*: the JoinPath created by this func.
|
|
*/
|
|
void add_nestloop_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype, JoinType save_jointype,
|
|
JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, SemiAntiJoinFactors* semifactors, Path* outer_path,
|
|
Path* inner_path, List* restrict_clauses, List* pathkeys, Relids required_outer, Distribution* target_distribution)
|
|
{
|
|
/* Full-outer join doesn't support nestloop yet */
|
|
AssertEreport(jointype != JOIN_FULL, MOD_OPT_JOIN, "Join type shouldn't be full join for nestloop");
|
|
|
|
bool redistribute_inner = false;
|
|
bool redistribute_outer = false;
|
|
bool replicate_inner = false;
|
|
bool replicate_outer = false;
|
|
List* distribute_keys_inner = NIL;
|
|
List* distribute_keys_outer = NIL;
|
|
List* joinclauses = NIL;
|
|
RelOptInfo* outerrel = NULL;
|
|
RelOptInfo* innerrel = NULL;
|
|
JoinPath* joinpath = NULL;
|
|
List* rrinfo_inner = NIL;
|
|
List* rrinfo_outer = NIL;
|
|
List* stream_distribute_key = NIL;
|
|
List* joinpath_list = NIL;
|
|
ListCell* lc = NULL;
|
|
NodeTag nodetag = T_NestLoop;
|
|
|
|
outerrel = outer_path->parent;
|
|
innerrel = inner_path->parent;
|
|
joinclauses = restrict_clauses;
|
|
|
|
distribute_keys_inner = inner_path->distribute_keys;
|
|
distribute_keys_outer = outer_path->distribute_keys;
|
|
|
|
if (is_replicated_path(outer_path))
|
|
replicate_outer = true;
|
|
|
|
if (is_replicated_path(inner_path))
|
|
replicate_inner = true;
|
|
|
|
if (joinclauses == NIL) {
|
|
/* clauseless join, should make sure we are dealing with distributed table */
|
|
redistribute_inner = true;
|
|
redistribute_outer = true;
|
|
} else {
|
|
redistribute_inner = is_distribute_need_on_joinclauses(
|
|
root, inner_path->distribute_keys, joinclauses, innerrel, outerrel, &rrinfo_inner);
|
|
redistribute_outer = is_distribute_need_on_joinclauses(
|
|
root, outer_path->distribute_keys, joinclauses, outerrel, innerrel, &rrinfo_outer);
|
|
}
|
|
|
|
/*
|
|
* Check node group distribution
|
|
* If path's distribution is different from target_distribution (computing node group), shuffle is needed
|
|
*/
|
|
redistribute_inner = redistribute_inner || ng_is_shuffle_needed(root, inner_path, target_distribution);
|
|
redistribute_outer = redistribute_outer || ng_is_shuffle_needed(root, outer_path, target_distribution);
|
|
|
|
/*
|
|
* If either side is replicated, join locally.
|
|
*/
|
|
if (replicate_outer || replicate_inner) {
|
|
/*
|
|
* Check if we need do further redistribution even with two replicate table
|
|
* and shuffle them to same computing node group.
|
|
*/
|
|
Path* outer_path_t = outer_path;
|
|
Path* inner_path_t = inner_path;
|
|
ng_stream_side_paths_for_replicate(
|
|
root, &outer_path_t, &inner_path_t, save_jointype, false, target_distribution);
|
|
|
|
if (NULL != outer_path_t && NULL != inner_path_t) {
|
|
if (!parallel_enable(inner_path_t, outer_path_t)) {
|
|
if (outer_path != outer_path_t || inner_path != inner_path_t) {
|
|
initial_cost_nestloop(
|
|
root, workspace, jointype, outer_path_t, inner_path_t, sjinfo, semifactors, 1);
|
|
}
|
|
|
|
joinpath = (JoinPath*)create_nestloop_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path_t,
|
|
inner_path_t,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_NONE,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path_t,
|
|
outer_path_t,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
NIL,
|
|
NIL);
|
|
}
|
|
|
|
bool can_redist = true;
|
|
foreach (lc, joinpath_list) {
|
|
joinpath = (JoinPath*)lfirst(lc);
|
|
can_redist = can_redist && add_replica_join_path(joinrel,
|
|
root,
|
|
save_jointype,
|
|
joinpath,
|
|
replicate_outer,
|
|
replicate_inner,
|
|
redistribute_inner,
|
|
redistribute_outer);
|
|
}
|
|
|
|
/* Redistribute join path is invalid. */
|
|
list_free_ext(joinpath_list);
|
|
if (!can_redist)
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Four scenarios
|
|
*/
|
|
Path* stream_path_inner = NULL;
|
|
Path* stream_path_outer = NULL;
|
|
List* inner_pathkeys = NIL;
|
|
List* outer_pathkeys = NIL;
|
|
|
|
if (pathkeys != NULL) {
|
|
inner_pathkeys = inner_path->pathkeys;
|
|
outer_pathkeys = outer_path->pathkeys;
|
|
}
|
|
|
|
if (redistribute_inner && !redistribute_outer) {
|
|
/*
|
|
* Three paths, redistribute inner or broadcast outer or broadcast inner(if redistribute inner is unavailable)
|
|
*/
|
|
{
|
|
double skew_stream = 0.0;
|
|
joinpath_list = NIL;
|
|
|
|
/* For redistribute, the distribute key should be in the targetlist of joinrel */
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_outer, innerrel->reltargetlist, inner_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
inner_pathkeys,
|
|
true,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_nestloop(
|
|
root, workspace, jointype, outer_path, stream_path_inner, sjinfo, semifactors, 1);
|
|
|
|
joinpath = (JoinPath*)create_nestloop_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
(Path*)stream_path_inner,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_REDISTRIBUTE,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
skew_stream,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
stream_distribute_key,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
|
|
add_path_list(joinpath_list, jointype, joinrel, root, distribute_keys_outer, stream_distribute_key);
|
|
}
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
inner_pathkeys,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_BROADCAST,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
|
|
if (can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
outer_pathkeys,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_NONE,
|
|
STREAM_BROADCAST,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
} else if (!redistribute_inner && redistribute_outer) {
|
|
/*
|
|
* Three paths, broadcast inner or redistribute outer or broadcast outer(if redistribute outer is unavailable)
|
|
*/
|
|
{
|
|
double skew_stream = 0.0;
|
|
joinpath_list = NIL;
|
|
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_inner, outerrel->reltargetlist, outer_path->parent, &skew_stream);
|
|
|
|
if (stream_distribute_key != NIL) {
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
outer_pathkeys,
|
|
false,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_nestloop(
|
|
root, workspace, jointype, stream_path_outer, inner_path, sjinfo, semifactors, 1);
|
|
|
|
joinpath = (JoinPath*)create_nestloop_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
(Path*)stream_path_outer,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_NONE,
|
|
STREAM_REDISTRIBUTE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
skew_stream,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
stream_distribute_key,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
|
|
add_path_list(joinpath_list, jointype, joinrel, root, stream_distribute_key, distribute_keys_inner);
|
|
}
|
|
}
|
|
|
|
if (can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
inner_pathkeys,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_BROADCAST,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
outer_pathkeys,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_NONE,
|
|
STREAM_BROADCAST,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
|
|
} else if (redistribute_inner && redistribute_outer) {
|
|
int i = joinrel->rel_dis_keys.matching_keys != NIL ? -1 : 0; /* loop start */
|
|
int key_num = list_length(joinrel->rel_dis_keys.superset_keys);
|
|
List* old_distribute_keys = NIL;
|
|
bool choose_optimal = false;
|
|
|
|
/*
|
|
* Three paths, broadcast inner or broadcast outer or redistribute inner and outer
|
|
*/
|
|
/*
|
|
* For redistribute path, we check all the matching key and superset keys
|
|
* to be distribute keys if possible. We check with the following sequence:
|
|
* (1) matching key; (2) superset key; (3) optimal key. We use variable i
|
|
* to track all process, with (1) i = -1; (2) i = 0 to key_num -1;
|
|
* (3) i = key_num. During whole process, we skip if distribute key is already
|
|
* used before. Also, if (3) is found in (1) and (2), we just skip (3).
|
|
*/
|
|
for (; i <= key_num; i++) {
|
|
List* redistribute_keys_inner = NIL;
|
|
List* redistribute_keys_outer = NIL;
|
|
double skew_outer = 0.0;
|
|
double skew_inner = 0.0;
|
|
List* desired_keys = NIL;
|
|
joinpath_list = NIL;
|
|
|
|
if (i == -1)
|
|
desired_keys = joinrel->rel_dis_keys.matching_keys;
|
|
else if (i < key_num)
|
|
desired_keys = (List*)list_nth(joinrel->rel_dis_keys.superset_keys, i);
|
|
|
|
if (i == key_num && choose_optimal)
|
|
continue;
|
|
|
|
/* Determine which clause both sides redistribute on */
|
|
get_distribute_keys(root,
|
|
joinclauses,
|
|
outer_path,
|
|
inner_path,
|
|
&skew_outer,
|
|
&skew_inner,
|
|
&redistribute_keys_outer,
|
|
&redistribute_keys_inner,
|
|
desired_keys,
|
|
(i == -1));
|
|
|
|
if (redistribute_keys_inner != NIL && redistribute_keys_outer != NIL) {
|
|
if (skew_outer <= 1.0 && skew_inner <= 1.0)
|
|
choose_optimal = true;
|
|
|
|
if (list_member(old_distribute_keys, redistribute_keys_outer))
|
|
continue;
|
|
else
|
|
old_distribute_keys = lappend(old_distribute_keys, redistribute_keys_outer);
|
|
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
redistribute_keys_inner,
|
|
inner_pathkeys,
|
|
true,
|
|
skew_inner,
|
|
target_distribution);
|
|
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
redistribute_keys_outer,
|
|
outer_pathkeys,
|
|
false,
|
|
skew_outer,
|
|
target_distribution);
|
|
|
|
initial_cost_nestloop(
|
|
root, workspace, jointype, stream_path_outer, stream_path_inner, sjinfo, semifactors, 1);
|
|
|
|
joinpath = (JoinPath*)create_nestloop_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
(Path*)stream_path_outer,
|
|
(Path*)stream_path_inner,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_REDISTRIBUTE,
|
|
STREAM_REDISTRIBUTE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
skew_inner,
|
|
skew_outer,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
redistribute_keys_inner,
|
|
redistribute_keys_outer,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
|
|
add_path_list(joinpath_list,
|
|
jointype,
|
|
joinrel,
|
|
root,
|
|
redistribute_keys_outer,
|
|
redistribute_keys_inner,
|
|
desired_keys,
|
|
(i == -1));
|
|
}
|
|
}
|
|
list_free_ext(old_distribute_keys);
|
|
|
|
if (can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
inner_pathkeys,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_BROADCAST,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
if (can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
outer_pathkeys,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_NONE,
|
|
STREAM_BROADCAST,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
} else if (!redistribute_inner && !redistribute_outer) {
|
|
/*
|
|
* if redistribute on different join key, still need to redistribute either one.
|
|
*/
|
|
if (rrinfo_inner != NULL && rrinfo_outer != NULL && !equal(rrinfo_inner, rrinfo_outer)) {
|
|
/*
|
|
* The distribute_keys_inner should be identical to innerpath->distribute_keys here.
|
|
* The distribute_keys_outer should be identical to outerpath->distribute_keys here.
|
|
*/
|
|
{
|
|
double skew_stream = 0.0;
|
|
joinpath_list = NIL;
|
|
|
|
/* For redistribute, the distribute key should be in the targetlist of joinrel */
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_outer, innerrel->reltargetlist, inner_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
inner_pathkeys,
|
|
true,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_nestloop(
|
|
root, workspace, jointype, outer_path, stream_path_inner, sjinfo, semifactors, 1);
|
|
|
|
joinpath = (JoinPath*)create_nestloop_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
(Path*)stream_path_inner,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_REDISTRIBUTE,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
skew_stream,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
stream_distribute_key,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
|
|
add_path_list(joinpath_list, jointype, joinrel, root, distribute_keys_outer, stream_distribute_key);
|
|
}
|
|
}
|
|
|
|
{
|
|
double skew_stream = 0.0;
|
|
joinpath_list = NIL;
|
|
|
|
/* For redistribute, the distribute key should be in the targetlist of joinrel */
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_inner, outerrel->reltargetlist, outer_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
outer_pathkeys,
|
|
false,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_nestloop(
|
|
root, workspace, jointype, stream_path_outer, inner_path, sjinfo, semifactors, 1);
|
|
|
|
joinpath = (JoinPath*)create_nestloop_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
(Path*)stream_path_outer,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_NONE,
|
|
STREAM_REDISTRIBUTE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
skew_stream,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
stream_distribute_key,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
|
|
add_path_list(joinpath_list, jointype, joinrel, root, stream_distribute_key, distribute_keys_inner);
|
|
}
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
inner_pathkeys,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_BROADCAST,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
if (!parallel_enable(inner_path, outer_path))
|
|
add_nestloop_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
outer_pathkeys,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
else
|
|
add_join_parallel_path(STREAM_NONE,
|
|
STREAM_BROADCAST,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
} else {
|
|
joinpath_list = NIL;
|
|
if (!parallel_enable(inner_path, outer_path)) {
|
|
joinpath = (JoinPath*)create_nestloop_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
semifactors,
|
|
outer_path,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer);
|
|
joinpath_list = lappend(joinpath_list, (void*)joinpath);
|
|
} else {
|
|
joinpath_list = add_join_parallel_path(STREAM_NONE,
|
|
STREAM_NONE,
|
|
root,
|
|
joinrel,
|
|
sjinfo,
|
|
semifactors,
|
|
inner_path,
|
|
outer_path,
|
|
1.0,
|
|
1.0,
|
|
jointype,
|
|
save_jointype,
|
|
required_outer,
|
|
workspace,
|
|
replicate_inner,
|
|
replicate_outer,
|
|
NIL,
|
|
NIL,
|
|
pathkeys,
|
|
restrict_clauses,
|
|
nodetag,
|
|
target_distribution,
|
|
inner_pathkeys,
|
|
outer_pathkeys);
|
|
}
|
|
add_path_list(joinpath_list, jointype, joinrel, root, distribute_keys_outer, distribute_keys_inner);
|
|
}
|
|
}
|
|
|
|
list_free_ext(rrinfo_inner);
|
|
list_free_ext(rrinfo_outer);
|
|
}
|
|
|
|
static void add_mergejoin_broadcast_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype,
|
|
JoinType save_jointype, JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, Path* need_stream_path,
|
|
Path* non_stream_path, List* restrict_clauses, List* pathkeys, Relids required_outer, List* mergeclauses,
|
|
List* outersortkeys, List* innersortkeys, List* stream_pathkeys, List* non_stream_pathkeys, bool is_replicate,
|
|
bool stream_outer, Distribution* target_distribution)
|
|
{
|
|
Path* streamed_path = NULL;
|
|
JoinPath* joinpath = NULL;
|
|
Path* new_outer_path = NULL;
|
|
Path* new_inner_path = NULL;
|
|
|
|
/* target_distribution would be NULL in SMP path, set it to default group of current mode */
|
|
if (NULL == target_distribution) {
|
|
target_distribution = ng_get_default_computing_group_distribution();
|
|
}
|
|
|
|
streamed_path = stream_side_path(root,
|
|
need_stream_path,
|
|
save_jointype,
|
|
is_replicate,
|
|
STREAM_BROADCAST,
|
|
NIL,
|
|
stream_pathkeys,
|
|
!stream_outer,
|
|
1.0,
|
|
target_distribution);
|
|
|
|
/* non-broadcast side also needs shuffle if node group is un-matched */
|
|
non_stream_path = ng_stream_non_broadcast_side_for_join(
|
|
root, non_stream_path, save_jointype, non_stream_pathkeys, is_replicate, stream_outer, target_distribution);
|
|
if (NULL == non_stream_path) {
|
|
/* non-broadcast side can not shuffle */
|
|
return;
|
|
}
|
|
|
|
new_outer_path = stream_outer ? streamed_path : non_stream_path;
|
|
new_inner_path = stream_outer ? non_stream_path : streamed_path;
|
|
|
|
initial_cost_mergejoin(
|
|
root, workspace, jointype, mergeclauses, new_outer_path, new_inner_path, outersortkeys, innersortkeys, sjinfo);
|
|
|
|
joinpath = (JoinPath*)create_mergejoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
new_outer_path,
|
|
new_inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys);
|
|
|
|
joinpath->path.distribute_keys = non_stream_path->distribute_keys;
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
}
|
|
|
|
void add_mergejoin_path(PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype, JoinType save_jointype,
|
|
JoinCostWorkspace* workspace, SpecialJoinInfo* sjinfo, Path* outer_path, Path* inner_path, List* restrict_clauses,
|
|
List* pathkeys, Relids required_outer, List* mergeclauses, List* outersortkeys, List* innersortkeys,
|
|
Distribution* target_distribution)
|
|
{
|
|
bool redistribute_inner = false;
|
|
bool redistribute_outer = false;
|
|
bool replicate_inner = false;
|
|
bool replicate_outer = false;
|
|
List* distribute_keys_inner = NIL;
|
|
List* distribute_keys_outer = NIL;
|
|
List* joinclauses = NIL;
|
|
RelOptInfo* outerrel = NULL;
|
|
RelOptInfo* innerrel = NULL;
|
|
JoinPath* joinpath = NULL;
|
|
List* rrinfo_inner = NULL;
|
|
List* rrinfo_outer = NULL;
|
|
List* stream_distribute_key = NIL;
|
|
|
|
/* Only create unparallel path for mergejoin. */
|
|
if (inner_path->dop > 1 || outer_path->dop > 1)
|
|
return;
|
|
|
|
outerrel = outer_path->parent;
|
|
innerrel = inner_path->parent;
|
|
joinclauses = restrict_clauses;
|
|
|
|
distribute_keys_inner = inner_path->distribute_keys;
|
|
distribute_keys_outer = outer_path->distribute_keys;
|
|
|
|
if (is_replicated_path(outer_path))
|
|
replicate_outer = true;
|
|
|
|
if (is_replicated_path(inner_path))
|
|
replicate_inner = true;
|
|
|
|
if (!replicate_inner || !replicate_outer) {
|
|
/* joinclauses of hashjoin should be Non-null. */
|
|
AssertEreport(joinclauses != NIL, MOD_OPT_JOIN, "Joinclauses of mergejoin should be Non-null");
|
|
|
|
redistribute_inner = is_distribute_need_on_joinclauses(
|
|
root, inner_path->distribute_keys, joinclauses, innerrel, outerrel, &rrinfo_inner);
|
|
redistribute_outer = is_distribute_need_on_joinclauses(
|
|
root, outer_path->distribute_keys, joinclauses, outerrel, innerrel, &rrinfo_outer);
|
|
}
|
|
|
|
/*
|
|
* Check node group distribution
|
|
* If path's distribution is different from target_distribution (computing node group), shuffle is needed
|
|
*/
|
|
redistribute_inner = redistribute_inner || ng_is_shuffle_needed(root, inner_path, target_distribution);
|
|
redistribute_outer = redistribute_outer || ng_is_shuffle_needed(root, outer_path, target_distribution);
|
|
|
|
/*
|
|
* If either side is replicated, join locally.
|
|
*/
|
|
if (replicate_outer || replicate_inner) {
|
|
/*
|
|
* Check if we need do further redistribution even with two replicate table
|
|
* and shuffle them to same computing node group.
|
|
*/
|
|
Path* outer_path_t = outer_path;
|
|
Path* inner_path_t = inner_path;
|
|
ng_stream_side_paths_for_replicate(
|
|
root, &outer_path_t, &inner_path_t, save_jointype, true, target_distribution);
|
|
|
|
if (NULL != outer_path_t && NULL != inner_path_t) {
|
|
if (outer_path != outer_path_t || inner_path != inner_path_t) {
|
|
initial_cost_mergejoin(root,
|
|
workspace,
|
|
jointype,
|
|
mergeclauses,
|
|
outer_path_t,
|
|
inner_path_t,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
sjinfo);
|
|
}
|
|
|
|
joinpath = (JoinPath*)create_mergejoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
outer_path_t,
|
|
inner_path_t,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys);
|
|
bool can_redistribute = true;
|
|
can_redistribute = add_replica_join_path(joinrel,
|
|
root,
|
|
save_jointype,
|
|
joinpath,
|
|
replicate_outer,
|
|
replicate_inner,
|
|
redistribute_inner,
|
|
redistribute_outer);
|
|
if (!can_redistribute)
|
|
return;
|
|
}
|
|
}
|
|
|
|
List* inner_pathkeys = inner_path->pathkeys;
|
|
List* outer_pathkeys = outer_path->pathkeys;
|
|
|
|
/*
|
|
* Four scenarios
|
|
*/
|
|
Path* stream_path_inner = NULL;
|
|
Path* stream_path_outer = NULL;
|
|
if (redistribute_inner && !redistribute_outer) {
|
|
/*
|
|
* Three paths, redistribute inner or broadcast outer or broadcast inner(if redistribute inner is unavailable)
|
|
*/
|
|
{
|
|
double skew_stream = 0.0;
|
|
|
|
/* For redistribute, the distribute key should be in the targetlist of joinrel */
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_outer, innerrel->reltargetlist, inner_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
inner_pathkeys,
|
|
true,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_mergejoin(root,
|
|
workspace,
|
|
jointype,
|
|
mergeclauses,
|
|
outer_path,
|
|
stream_path_inner,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
sjinfo);
|
|
|
|
joinpath = (JoinPath*)create_mergejoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
outer_path,
|
|
(Path*)stream_path_inner,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys);
|
|
|
|
if (jointype != JOIN_FULL) {
|
|
joinpath->path.distribute_keys =
|
|
locate_distribute_key(jointype, distribute_keys_outer, stream_distribute_key);
|
|
if (joinpath->path.distribute_keys)
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
} else
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
}
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
add_mergejoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
inner_path,
|
|
outer_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
inner_pathkeys,
|
|
outer_pathkeys,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
}
|
|
|
|
if (can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
add_mergejoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
outer_path,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
outer_pathkeys,
|
|
inner_pathkeys,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
}
|
|
} else if (!redistribute_inner && redistribute_outer) {
|
|
/*
|
|
* Three paths, broadcast inner or redistribute outer or broadcast outer(if redistribute outer is unavailable)
|
|
*/
|
|
{
|
|
double skew_stream = 0.0;
|
|
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_inner, outerrel->reltargetlist, outer_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
outer_pathkeys,
|
|
false,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_mergejoin(root,
|
|
workspace,
|
|
jointype,
|
|
mergeclauses,
|
|
stream_path_outer,
|
|
inner_path,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
sjinfo);
|
|
|
|
joinpath = (JoinPath*)create_mergejoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
(Path*)stream_path_outer,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys);
|
|
|
|
if (jointype != JOIN_FULL) {
|
|
joinpath->path.distribute_keys =
|
|
locate_distribute_key(jointype, stream_distribute_key, distribute_keys_inner);
|
|
if (joinpath->path.distribute_keys)
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
} else
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
}
|
|
}
|
|
|
|
if (can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
add_mergejoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
inner_path,
|
|
outer_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
inner_pathkeys,
|
|
outer_pathkeys,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
add_mergejoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
outer_path,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
outer_pathkeys,
|
|
inner_pathkeys,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
}
|
|
|
|
} else if (redistribute_inner && redistribute_outer) {
|
|
int i = joinrel->rel_dis_keys.matching_keys != NIL ? -1 : 0; /* loop start */
|
|
int key_num = list_length(joinrel->rel_dis_keys.superset_keys);
|
|
List* old_distribute_keys = NIL;
|
|
bool choose_optimal = false;
|
|
|
|
/*
|
|
* Three paths, broadcast inner or broadcast outer or redistribute inner and outer
|
|
*/
|
|
/*
|
|
* For redistribute path, we check all the matching key and superset keys
|
|
* to be distribute keys if possible. We check with the following sequence:
|
|
* (1) matching key; (2) superset key; (3) optimal key. We use variable i
|
|
* to track all process, with (1) i = -1; (2) i = 0 to key_num -1;
|
|
* (3) i = key_num. During whole process, we skip if distribute key is already
|
|
* used before. Also, if (3) is found in (1) and (2), we just skip (3).
|
|
*/
|
|
for (; i <= key_num; i++) {
|
|
List *redistribute_keys_inner = NIL, *redistribute_keys_outer = NIL;
|
|
double skew_outer = 0.0, skew_inner = 0.0;
|
|
List* desired_keys = NIL;
|
|
|
|
if (i == -1)
|
|
desired_keys = joinrel->rel_dis_keys.matching_keys;
|
|
else if (i < key_num)
|
|
desired_keys = (List*)list_nth(joinrel->rel_dis_keys.superset_keys, i);
|
|
|
|
if (i == key_num && choose_optimal)
|
|
continue;
|
|
|
|
/* Determine which clause both sides redistribute on */
|
|
get_distribute_keys(root,
|
|
joinclauses,
|
|
outer_path,
|
|
inner_path,
|
|
&skew_outer,
|
|
&skew_inner,
|
|
&redistribute_keys_outer,
|
|
&redistribute_keys_inner,
|
|
desired_keys,
|
|
(i == -1));
|
|
|
|
if (redistribute_keys_inner != NIL && redistribute_keys_outer != NIL) {
|
|
if (skew_outer <= 1.0 && skew_inner <= 1.0)
|
|
choose_optimal = true;
|
|
|
|
if (list_member(old_distribute_keys, redistribute_keys_outer))
|
|
continue;
|
|
else
|
|
old_distribute_keys = lappend(old_distribute_keys, redistribute_keys_outer);
|
|
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
redistribute_keys_inner,
|
|
inner_pathkeys,
|
|
true,
|
|
skew_inner,
|
|
target_distribution);
|
|
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
redistribute_keys_outer,
|
|
outer_pathkeys,
|
|
false,
|
|
skew_outer,
|
|
target_distribution);
|
|
|
|
initial_cost_mergejoin(root,
|
|
workspace,
|
|
jointype,
|
|
mergeclauses,
|
|
stream_path_outer,
|
|
stream_path_inner,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
sjinfo);
|
|
|
|
joinpath = (JoinPath*)create_mergejoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
(Path*)stream_path_outer,
|
|
(Path*)stream_path_inner,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys);
|
|
if (jointype != JOIN_FULL) {
|
|
joinpath->path.distribute_keys = locate_distribute_key(
|
|
jointype, redistribute_keys_outer, redistribute_keys_inner, desired_keys, (i == -1));
|
|
if (joinpath->path.distribute_keys)
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
} else
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
}
|
|
}
|
|
list_free_ext(old_distribute_keys);
|
|
|
|
if (can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
add_mergejoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
inner_path,
|
|
outer_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
inner_pathkeys,
|
|
outer_pathkeys,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
}
|
|
if (can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
add_mergejoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
outer_path,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
outer_pathkeys,
|
|
inner_pathkeys,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
}
|
|
} else if (!redistribute_inner && !redistribute_outer) {
|
|
/*
|
|
* if redistribute on different join key, still need to redistribute either one.
|
|
*/
|
|
if (rrinfo_inner != NULL && rrinfo_outer != NULL && !equal(rrinfo_inner, rrinfo_outer)) {
|
|
/*
|
|
* The distribute_keys_inner should be identical to innerpath->distribute_keys here.
|
|
* The distribute_keys_outer should be identical to outerpath->distribute_keys here.
|
|
*/
|
|
{
|
|
double skew_stream = 0.0;
|
|
|
|
/* For redistribute, the distribute key should be in the targetlist of joinrel */
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_outer, innerrel->reltargetlist, inner_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
stream_path_inner = stream_side_path(root,
|
|
inner_path,
|
|
save_jointype,
|
|
replicate_inner,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
inner_pathkeys,
|
|
true,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_mergejoin(root,
|
|
workspace,
|
|
jointype,
|
|
mergeclauses,
|
|
outer_path,
|
|
stream_path_inner,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
sjinfo);
|
|
|
|
joinpath = (JoinPath*)create_mergejoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
outer_path,
|
|
(Path*)stream_path_inner,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys);
|
|
|
|
if (jointype != JOIN_FULL) {
|
|
joinpath->path.distribute_keys =
|
|
locate_distribute_key(jointype, distribute_keys_outer, stream_distribute_key);
|
|
if (joinpath->path.distribute_keys)
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
} else
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
}
|
|
}
|
|
|
|
{
|
|
double skew_stream = 0.0;
|
|
|
|
/* For redistribute, the distribute key should be in the targetlist of joinrel */
|
|
stream_distribute_key =
|
|
get_otherside_key(root, rrinfo_inner, outerrel->reltargetlist, outer_path->parent, &skew_stream);
|
|
if (stream_distribute_key != NIL) {
|
|
stream_path_outer = stream_side_path(root,
|
|
outer_path,
|
|
save_jointype,
|
|
replicate_outer,
|
|
STREAM_REDISTRIBUTE,
|
|
stream_distribute_key,
|
|
outer_pathkeys,
|
|
false,
|
|
skew_stream,
|
|
target_distribution);
|
|
|
|
initial_cost_mergejoin(root,
|
|
workspace,
|
|
jointype,
|
|
mergeclauses,
|
|
stream_path_outer,
|
|
inner_path,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
sjinfo);
|
|
|
|
joinpath = (JoinPath*)create_mergejoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
(Path*)stream_path_outer,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys);
|
|
;
|
|
|
|
if (jointype != JOIN_FULL) {
|
|
joinpath->path.distribute_keys =
|
|
locate_distribute_key(jointype, stream_distribute_key, distribute_keys_inner);
|
|
if (joinpath->path.distribute_keys)
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
} else
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
}
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_inner(jointype, save_jointype, replicate_outer, distribute_keys_outer, outer_path)) {
|
|
add_mergejoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
inner_path,
|
|
outer_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
inner_pathkeys,
|
|
outer_pathkeys,
|
|
replicate_inner,
|
|
false,
|
|
target_distribution);
|
|
}
|
|
|
|
if (stream_distribute_key == NIL &&
|
|
can_broadcast_outer(jointype, save_jointype, replicate_inner, distribute_keys_inner, inner_path)) {
|
|
add_mergejoin_broadcast_path(root,
|
|
joinrel,
|
|
jointype,
|
|
save_jointype,
|
|
workspace,
|
|
sjinfo,
|
|
outer_path,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys,
|
|
outer_pathkeys,
|
|
inner_pathkeys,
|
|
replicate_outer,
|
|
true,
|
|
target_distribution);
|
|
}
|
|
} else {
|
|
joinpath = (JoinPath*)create_mergejoin_path(root,
|
|
joinrel,
|
|
jointype,
|
|
workspace,
|
|
sjinfo,
|
|
outer_path,
|
|
inner_path,
|
|
restrict_clauses,
|
|
pathkeys,
|
|
required_outer,
|
|
mergeclauses,
|
|
outersortkeys,
|
|
innersortkeys);
|
|
if (jointype != JOIN_FULL) {
|
|
joinpath->path.distribute_keys =
|
|
locate_distribute_key(jointype, distribute_keys_outer, distribute_keys_inner);
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
} else
|
|
add_path(root, joinrel, (Path*)joinpath);
|
|
}
|
|
}
|
|
|
|
list_free_ext(rrinfo_inner);
|
|
list_free_ext(rrinfo_outer);
|
|
}
|
|
|
|
/* needs_agg_stream
|
|
* judge if redistribution is needed for specific distribute key
|
|
*
|
|
* Parameters:
|
|
* @in root: Planner info structure of current query level
|
|
* @in tlist: targetlist with group by expr in it, others are agg exprs
|
|
* @in distribute_targetlist: distribute key of current plan
|
|
*
|
|
* Returns: true if we need redistribution, else false
|
|
*/
|
|
bool needs_agg_stream(PlannerInfo* root, List* tlist, List* distribute_targetlist)
|
|
{
|
|
ListCell* lc_agg = NULL;
|
|
ListCell* lc_key = NULL;
|
|
|
|
if (distribute_targetlist == NULL) {
|
|
return true;
|
|
}
|
|
|
|
/* Check the distribute key first */
|
|
foreach (lc_key, distribute_targetlist) {
|
|
Node* v = (Node*)lfirst(lc_key);
|
|
|
|
foreach (lc_agg, tlist) {
|
|
Node* te = (Node*)lfirst(lc_agg);
|
|
Node* expr = NULL;
|
|
|
|
if (IsA(te, TargetEntry))
|
|
expr = (Node*)((TargetEntry*)te)->expr;
|
|
else
|
|
expr = te;
|
|
|
|
if (judge_node_compatible(root, v, expr))
|
|
break;
|
|
}
|
|
|
|
/* doesn't find any equal expr for current distribute key expr, so need redistribute */
|
|
if (NULL == lc_agg) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* find equal expr for every distribute key expr */
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* equal_distributekey:
|
|
* Judge if two distribute keys are semantically equal
|
|
* Parameters:
|
|
* @in root: planner info of current query level
|
|
* @distribute_key1: compared distribute key 1
|
|
* @distribute_key2: compared distribute key 2
|
|
* Return:
|
|
* true if two distribute keys are semantically equal, else false
|
|
*/
|
|
bool equal_distributekey(PlannerInfo* root, List* distribute_key1, List* distribute_key2)
|
|
{
|
|
ListCell* lc1 = NULL;
|
|
ListCell* lc2 = NULL;
|
|
|
|
if (list_length(distribute_key1) != list_length(distribute_key2))
|
|
return false;
|
|
|
|
forboth(lc1, distribute_key1, lc2, distribute_key2)
|
|
{
|
|
Node* key1 = (Node*)lfirst(lc1);
|
|
Node* key2 = (Node*)lfirst(lc2);
|
|
|
|
/* check if key1 and key2 from same eq members */
|
|
if (!judge_node_compatible(root, key1, key2))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* judge_node_compatible
|
|
* Judge if two nodes are from the same equivalence class and
|
|
* hash type compatible
|
|
* Parameters:
|
|
* @in root: planner info of current query level
|
|
* @in n1: compared node 1
|
|
* @in n2: compared node 2
|
|
* Return:
|
|
* true if two nodes are from same equivalence class, else false
|
|
*/
|
|
bool judge_node_compatible(PlannerInfo* root, Node* n1, Node* n2)
|
|
{
|
|
ListCell* lc = NULL;
|
|
|
|
if (equal(n1, n2))
|
|
return true;
|
|
if (!is_compatible_type(exprType(n1), exprType(n2)))
|
|
return false;
|
|
if (root == NULL)
|
|
return false;
|
|
|
|
foreach (lc, root->eq_classes) {
|
|
EquivalenceClass* ec = (EquivalenceClass*)lfirst(lc);
|
|
bool found1 = find_ec_memeber_for_var(ec, n1);
|
|
bool found2 = find_ec_memeber_for_var(ec, n2);
|
|
if (found1 && found2)
|
|
break;
|
|
else if (found1 || found2)
|
|
return false;
|
|
}
|
|
if (lc == NULL)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|