/* * Copyright (c) 2020 Huawei Technologies Co.,Ltd. * Portions Copyright (c) 2021, openGauss Contributors * * openGauss is licensed under Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: * * http://license.coscl.org.cn/MulanPSL2 * * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. * --------------------------------------------------------------------------------------- * * planstartwith.cpp * The query optimizer external interface. * * IDENTIFICATION * src/gausskernel/optimizer/plan/planstartwith.cpp * * --------------------------------------------------------------------------------------- */ #include "postgres.h" #include "knl/knl_variable.h" #include #include #include "access/transam.h" #include "catalog/indexing.h" #include "catalog/pg_operator.h" #include "catalog/pg_constraint.h" #include "catalog/pgxc_group.h" #include "catalog/pgxc_node.h" #include "executor/executor.h" #include "executor/node/nodeAgg.h" #include "miscadmin.h" #include "lib/bipartite_match.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/primnodes.h" #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/nodegroups.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/subselect.h" #include "optimizer/tlist.h" #include "parser/analyze.h" #include "parser/parsetree.h" #include "parser/parse_agg.h" #include "rewrite/rewriteManip.h" #include "securec.h" #include "utils/rel.h" #include "commands/prepare.h" #include "pgxc/pgxc.h" #include "optimizer/pgxcplan.h" #include "optimizer/streamplan.h" #include "workload/workload.h" #include "optimizer/streamplan.h" #include "utils/relcache.h" #include "utils/selfuncs.h" #include "utils/fmgroids.h" #include "access/heapam.h" #include "vecexecutor/vecfunc.h" #include "executor/node/nodeRecursiveunion.h" #include "executor/node/nodeCtescan.h" #include "optimizer/randomplan.h" #include "optimizer/optimizerdebug.h" #include "parser/parse_oper.h" #include "parser/parse_expr.h" extern Node* preprocess_expression(PlannerInfo* root, Node* expr, int kind); StartWithCTEPseudoReturnColumns g_StartWithCTEPseudoReturnColumns[] = { {"level", INT4OID, -1, InvalidOid}, {"connect_by_isleaf", INT4OID, -1, InvalidOid}, {"connect_by_iscycle", INT4OID, -1, InvalidOid}, {"rownum", INT4OID, -1, InvalidOid} }; /* * -------------------------------------------------------------------------------------- * Data & Local routines support major planning for start with * -------------------------------------------------------------------------------------- */ static StartWithOp *CreateStartWithOpNode(PlannerInfo *root, CteScan *cteplan, RecursiveUnion *ruplan); static void ProcessConnectByFakeConst(PlannerInfo *root, StartWithOp *swplan); static void BuildStartWithInternalTargetList(PlannerInfo *root, CteScan *cteplan, StartWithOp *swplan); static void ProcessOrderSiblings(PlannerInfo *root, StartWithOp *swplan); static void OptimizeStartWithPlan(PlannerInfo *root, StartWithOp *swplan); #define SWCB_MATERIAL_OUTER_THREADHOLD 100 static inline bool NeedMaterialJoinOuter(Plan *outer, Plan *inner) { return (outer->total_cost > inner->total_cost * SWCB_MATERIAL_OUTER_THREADHOLD); } /* * -------------------------------------------------------------------------------------- * Data & Local routines support internal Key/Col generation and also * -------------------------------------------------------------------------------------- */ typedef struct PullUpConnectByFuncVarContext { PlannerInfo *root; List *pullupVars; CteScan *cteplan; StartWithOp *swplan; } PullUpConnectByFuncVarContext; typedef struct PullUpConnectByFuncExprContext { List *pullupFuncs; } PullUpConnectByFuncExprContext; static List *GetPRCTargetEntryList(PlannerInfo *root, RangeTblEntry *rte, StartWithOp *swplan); static int GetVarPRCType(List *prcList, const Var* var); static void MarkPRCNotSkip(StartWithOp *swplan, int prcType); typedef struct ReplaceFakeConstContext { Var *levelVar; Var *rownumVar; Node **nodeRef; } ReplaceFakeConstContext; static bool PullUpConnectByFuncVarsWalker(Node *node, PullUpConnectByFuncVarContext *context); static List *PullUpConnectByFuncVars(PlannerInfo *root, CteScan *cteScan, Node *targetEntry); static void CheckInvalidConnectByfuncArgs(CteScan *cteplan, Oid funcid, List *arg_vars); static void GenerateStartWithInternalEntries(PlannerInfo *root, CteScan *cteplan, List **keyEntryList, List **colEntryList); static List* BuildStartWithPlanPseudoTargetList(Plan *plan, Index varno, List *key_list, List *col_list, bool needSiblings); static inline bool StartWithNeedSiblingSort(PlannerInfo *root, CteScan *cteplan) { Assert (IsA(cteplan, CteScan) && IsCteScanProcessForStartWith(cteplan)); return (cteplan->cteRef->swoptions->siblings_orderby_clause != NULL); } /* * -------------------------------------------------------------------------------------- * Data & Local routines supporot ORDER SIBLING BY * -------------------------------------------------------------------------------------- */ #define SORTCMP_TOTAL_NUM 3 #define SORTCMP_LT 0 #define SORTCMP_EQ 1 #define SORTCMP_GT 2 #define CF_ONE 1 #define CF_TWO 2 #define TEXT_COLLCATION 100 typedef struct OrderSiblingSortEntry { TargetEntry *tle; bool sortCmpOp[SORTCMP_TOTAL_NUM]; bool sortByNullsFirst; } OrderSiblingSortEntry; static OrderSiblingSortEntry* CreateOrderSiblingSortEntry( TargetEntry *entry, SortByDir dir); static Sort *CreateSiblingsSortPlan(PlannerInfo* root, Plan* lefttree, List *sortEntryList, double limit_tuples); static Sort *CreateSortPlanUnderRU(PlannerInfo* root, Plan* lefttree, List *siblings, double limit_tuples); static char *CheckAndFixSiblingsColName(PlannerInfo *root, Plan *basePlan, char *colname, TargetEntry *te); static char *GetOrderSiblingsColName(PlannerInfo* root, SortBy *sb, Plan *basePlan); /* * -------------------------------------------------------------------------------------- * Data & Local routines supporot pseudo targetlist push down * -------------------------------------------------------------------------------------- */ #define UnknownVarno 0 #define MAX_PLAN_DEPTH 100 typedef struct StackNode { Plan *value_array[MAX_PLAN_DEPTH]; int top; } StackNode; static inline void StackNodePush(StackNode *s, Plan *node) { s->top++; s->value_array[s->top] = node; } static inline Plan* StackNodePop(StackNode *s) { if (s->top < 0) { elog(ERROR, "error to pop() element in %s", __FUNCTION__); } Plan *node = s->value_array[s->top]; s->value_array[s->top] = NULL; s->top--; return node; } typedef struct StackVarno { Index value_array[MAX_PLAN_DEPTH]; int top; } StackVarno; static inline void StackVarnoPush(StackVarno *s, Index varno) { s->top++; s->value_array[s->top] = varno; } static inline Index StackVarnoPop(StackVarno *s) { if (s->top < 0) { elog(ERROR, "error to pop() element in %s", __FUNCTION__); } Index varno = s->value_array[s->top]; s->value_array[s->top] = 0; s->top--; return varno; } typedef struct PlanAccessPathSearchContext { Plan *topNode; Plan *botNode; List *fullEntryList; StackNode planStack; StackVarno varnoStack; Index relid; bool done; int numsPrevTlist; } PlanAccessPathSearchContext; static void GetWorkTableScanPlanPath(PlannerInfo *root, Plan *node, PlanAccessPathSearchContext *context); static void BindPlanNodePseudoEntries(PlannerInfo *root, Plan *node, Index varno, PlanAccessPathSearchContext *context); static void AddPseudoEntries(Plan *plan, Index relid, PlanAccessPathSearchContext *context); /* * -------------------------------------------------------------------------------------- * EXPORT functions * -------------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------------- * - brief: A helper function to determin if we need add pseudo *TLE* on CteScan node * to fetch information to support start-with processing. Normally, if a CteScan * node transfomed from Start/With with ORDER-SIBLINGS-BY, LEVEL, ISLEAF, * ISCYCLE we need do so * ------------------------------------------------------------------------------------- */ bool IsCteScanProcessForStartWith(CteScan *ctescan) { if (!IsA((Plan *)ctescan, CteScan)) { return false; } return (ctescan->cteRef != NULL && ctescan->cteRef->swoptions != NULL); } /* * ------------------------------------------------------------------------------------- * - brief: A helper function to indicate if a target entry is for SWCB's internal * target entry, e.g. RUTIR, array_key, array_col. * * - return: TRUE if tle->resname is RUITR/array_key/array_col * ------------------------------------------------------------------------------------- */ bool IsPseudoInternalTargetEntry(const TargetEntry *tle) { if (tle == NULL || tle->resname == NULL) { return false; } StartWithOpColumnType type = GetPseudoColumnType(tle); return (type == SWCOL_RUITR || type == SWCOL_ARRAY_KEY || type == SWCOL_ARRAY_COL || type == SWCOL_ARRAY_SIBLINGS); } /* * ------------------------------------------------------------------------------------- * - brief: A helper function to indicate if a target entry is for SWCB's pseudo return * columns * * - return: TRUE if tle->resname is level/rownum/iscycle/isleaf * ------------------------------------------------------------------------------------- */ bool IsPseudoReturnTargetEntry(const TargetEntry *tle) { if (tle == NULL || tle->resname == NULL) { return false; } StartWithOpColumnType type = GetPseudoColumnType(tle); return (type == SWCOL_LEVEL || type == SWCOL_ISLEAF || type == SWCOL_ISCYCLE || type == SWCOL_ROWNUM); } /* * ------------------------------------------------------------------------------------- * - brief: function to help fix tlist with explicite name * * - return: return a new list with resname assigned * ------------------------------------------------------------------------------------- */ List *FixSwTargetlistResname(PlannerInfo *root, RangeTblEntry *curRte, List *tlist) { if (curRte->rtekind != RTE_CTE || !curRte->swConverted) { elog(WARNING, "unusable case just original tlist"); return tlist; } int baseColNum = list_length(curRte->eref->colnames); int natts = list_length(tlist); if (natts - baseColNum != STARTWITH_PSEUDO_RETURN_ATTNUMS && baseColNum != natts) { elog(ERROR, "unrecognized case baseColNum/tlist"); } /* attach resname for target entry */ ListCell *lc = NULL; List *newList = NIL; AttrNumber attno = 0; /* fix the origianl output column */ foreach (lc, tlist) { TargetEntry *entry = (TargetEntry *)lfirst(lc); const char *colname = NULL; if (attno < baseColNum) { colname = strVal(list_nth(curRte->eref->colnames, attno)); } else { colname = g_StartWithCTEPseudoReturnColumns[attno - baseColNum].colname; } entry->resname = pstrdup(colname); attno++; newList = lappend(newList, entry); } return newList; } /* * @Brief: identify if it is a connect by level/rownum plan node */ bool IsConnectByLevelStartWithPlan(const StartWithOp *plan) { Assert (IsA(plan, StartWithOp) && plan->swoptions != NULL); return (plan->swoptions->connect_by_type == CONNECT_BY_LEVEL || plan->swoptions->connect_by_type == CONNECT_BY_ROWNUM || plan->swoptions->connect_by_type == CONNECT_BY_MIXED_LEVEL || plan->swoptions->connect_by_type == CONNECT_BY_MIXED_ROWNUM); } /* * @Brief: return a INT const node value, 0 if not INT const, considered as exception */ int32 GetStartWithFakeConstValue(A_Const *n) { Assert (IsA(n, A_Const)); if (n->val.type == T_Integer) { return n->val.val.ival; } else { elog(LOG, "Accept a other Const val when evaluate FakeConst to rownum/level"); return 0; } } /* * @Brief: return a INT const node value, 0 if not INT const, considered as exception */ int32 GetStartWithFakeConstValue(Const *val) { Assert (IsA(val, Const)); if (IsA(val, Const) && !((Const*)val)->constisnull && ((Const*)val)->consttype == INT4OID) { return DatumGetInt32(((Const*)val)->constvalue); } else { elog(LOG, "Accept an invalid Const val when evaluate FakeConst to rownum/level"); return 0; } } static char* GetSiblingsColNameFromFunc(Node* node) { if (!IsA(node, FuncCall)) { return NULL; } ListCell* lc = NULL; foreach(lc, ((FuncCall*) node)->args) { Node *n = (Node *) lfirst(lc); if (!IsA(n, ColumnRef)) { continue; } ColumnRef *cr = (ColumnRef *) n; int len = list_length(cr->fields); if (len == CF_ONE) { return strVal(linitial(cr->fields)); } else if (len == CF_TWO) { return strVal(lsecond(cr->fields)); } } return NULL; } /* * @Brief: check fix order-siblings columns, normally we can find sort-key from from * basePlan's targetlist, but some special case we need additional process: * 1. check colname exists in base-plan's targetlist, return if ok * 2. check colname exists in parse->targetlist's rename * un-named expr, assign "?column?" as default * 3. alias name * * For these cases, we need scan parse->targetlist(alias case) and find the basePlan's * to recreate sort key */ static char *CheckAndFixSiblingsColName(PlannerInfo *root, Plan *basePlan, char *colname, TargetEntry *te) { /* step1. check if searched colname exists in base-plan's targetlist */ char *resultColName = NULL; ListCell *lc = NULL; foreach (lc, basePlan->targetlist) { TargetEntry *entry = (TargetEntry *)lfirst(lc); if (entry->resname == NULL) { continue; } char *label = strrchr(entry->resname, '@'); label += 1; if (strcmp(colname, label) == 0) { resultColName = colname; break; } } /* * If resultColName is found in basePlan's targelist, it says we are in regular * case and it is safely to build sort plan for Order-Sibling clause * * regular case, we found sort-entry in base-plan's targetlist */ if (resultColName != NULL) { return resultColName; } /* * step2. try to find sort-entry in parse->targetlist as resname(for alias case) * case 1. regular alias, e.g. select id as alias_id,... te->resname: "alias_id" * case 2. unnamed expr, e.g. select id * 2, pid,.... te->resname: "?column?" */ bool found = false; if (te == NULL) { /* normally for alias case */ foreach (lc, root->parse->targetList) { te = (TargetEntry *)lfirst(lc); /* te->resname may be null, so check it first */ if (te->resname && strcmp(te->resname, colname) == 0) { found = true; break; } } /* * Still not found just return origin colname, error-out later in sort-plan * creation stage */ if (!found || te == NULL) { return colname; } } /* Pull the aggregates and var nodes from the quals */ List *vars = pull_var_clause((Node *)te->expr, PVC_INCLUDE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS); /* * do not support none-table column's ref specified as order siblings's sort entry * * Keep origin behavior as previous version */ if (vars == NIL) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("expression with none-var in order siblings is not supported"))); } /* do not support multi-column refs specified as order sibling's sort entry */ if (list_length(vars) > 1) { ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("expression with multi-vars in order siblings is not full-supported," " only 1st var will be sorted."))); } Var *var = (Var *)linitial(vars); RangeTblEntry *rte = root->simple_rte_array[var->varno]; char *raw_cte_alias = (char *)strVal(list_nth(rte->eref->colnames, var->varattno - 1)); resultColName = strrchr(raw_cte_alias, '@'); resultColName += 1; /* fix '@' offset */ return resultColName; } /* * @Brief: Get Siblings column name according Siblings-SortBy Clause. */ static char *GetOrderSiblingsColName(PlannerInfo* root, SortBy *sb, Plan *basePlan) { char *colname = NULL; TargetEntry *te = NULL; if (IsA(sb->node, ColumnRef)) { ColumnRef *cr = (ColumnRef *)sb->node; int len = list_length(cr->fields); if (len == CF_ONE) { colname = strVal(linitial(cr->fields)); } else if (len == CF_TWO) { colname = strVal(lsecond(cr->fields)); } } else if (IsA(sb->node, A_Const)) { A_Const *con = (A_Const *)sb->node; Value* val = &((A_Const*)con)->val; if (!IsA(val, Integer)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("not integer constant in order siblings by clause"))); } int siblingIdx = intVal(val); if (siblingIdx > list_length(root->parse->targetList)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Order siblings by tlistIdx %d exceed length of targetList.", siblingIdx))); } te = (TargetEntry *)list_nth(root->parse->targetList, siblingIdx - 1); if (IsA(te->expr, FuncExpr) && IsStartWithFunction((FuncExpr *)te->expr)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Not support refer startwithfunc column in order siblings by."))); } if (te->resname != NULL && IsPseudoReturnColumn(te->resname)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Not support refer startwith Pseudo column in order siblings by."))); } colname = te->resname; } /* * We do not support function call as sort key here yet. * Try to do our best by returning the first arg column anyway. */ if (colname == NULL) { colname = GetSiblingsColNameFromFunc(sb->node); } /* * Check and fix none regular columns, e.g. expr with no alias ?column? and * name-alias that can not be found from StartWithOp plan's targetlist * * Special case: we do not have to process PRC */ if (colname != NULL && !IsPseudoReturnColumn(colname)) { colname = CheckAndFixSiblingsColName(root, basePlan, colname, te); } return colname; } static Node* tryReplaceFakeConstWithTarget(Node* node, ReplaceFakeConstContext *context) { if (node == NULL) { return node; } if (IsA(node, Const)) { int constVal = GetStartWithFakeConstValue((Const *)node); if (constVal == CONNECT_BY_LEVEL_FAKEVALUE) { node = (Node*) context->levelVar; } else if (constVal == CONNECT_BY_ROWNUM_FAKEVALUE) { node = (Node *) context->rownumVar; } } return node; } static bool ReplaceFakeConstWalker(Node *node, ReplaceFakeConstContext *context) { if (node == NULL) { return false; } switch (node->type) { case T_OpExpr: { OpExpr *op = (OpExpr *)node; List *newArgs = NIL; ListCell *lc = NULL; /* replace "fake-const" to level/rownum var attr */ foreach (lc, op->args) { Node *n = (Node *)lfirst(lc); n = tryReplaceFakeConstWithTarget(n, context); newArgs = lappend(newArgs, n); } op->args= newArgs; break; } case T_TypeCast: { TypeCast* tc = (TypeCast*) node; tc->arg = tryReplaceFakeConstWithTarget(tc->arg, context); break; } case T_ScalarArrayOpExpr: { ScalarArrayOpExpr* opexpr = (ScalarArrayOpExpr*)node; ListCell *lc = NULL; List *newArgs = NIL; foreach (lc, opexpr->args) { Node *n = (Node *)lfirst(lc); n = tryReplaceFakeConstWithTarget(n, context); newArgs = lappend(newArgs, n); } opexpr->args = newArgs; break; } case T_FuncExpr: { FuncExpr* fexpr = (FuncExpr*)node; List *newArgs = NIL; ListCell *lc = NULL; /* replace "fake-const" to level/rownum var attr */ foreach (lc, fexpr->args) { Node *n = (Node *)lfirst(lc); n = tryReplaceFakeConstWithTarget(n, context); newArgs = lappend(newArgs, n); } fexpr->args= newArgs; break; } default: { ereport(DEBUG1, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("StartWithOp's ConnectByLevel only suport simple connect-by expr"), errdetail("Maybe you see the support scope of connect by level"), errhint("Please check your connect by clause carefully"))); break; } } return expression_tree_walker(node, (bool (*)())ReplaceFakeConstWalker, (void*)context); } /* * ------------------------------------------------------------------------------------- * @Brief: replace the fake-const value into real level/rownum attribute, so that we can * do level/rownum condition check regularly * * Revisit the whole implementation for connect-by level/rownum: * (1). In syntax stage, we convert level/rownum column into a "fake-const" * (2). In semantic stage, as we don't add level/rownum pseodu return column to * base table, so a "fake-const" value in connect-by expression ensure * safe-transform * (3). In planning state(StartWithOp), level/rownum pseudo column is inherited from * CteScan, so we replace "fake-const" back to its correct format and do * expression preprocess, SSQ/CSSQ can be proper planned into InitPlan/SubPlan * ------------------------------------------------------------------------------------- */ static void ReplaceFakeConst(PlannerInfo *root, StartWithOp *swplan) { StartWithOptions *swoptions = swplan->swoptions; Node *connectByLevelExpr = swoptions->connect_by_level_quals; /* Initialize replacement context */ errno_t rc = 0; ReplaceFakeConstContext context; rc = memset_s(&context, sizeof(ReplaceFakeConstContext), 0, sizeof(ReplaceFakeConstContext)); securec_check(rc, "\0", "\0"); /* Step 1. Find level/rownum pseudo return columns from startwith's targetlist */ ListCell *lc = NULL; foreach (lc, swplan->plan.targetlist) { TargetEntry *entry = (TargetEntry *)lfirst(lc); if (pg_strcasecmp(entry->resname, "level") == 0) { context.levelVar = (Var *)entry->expr; } if (pg_strcasecmp(entry->resname, "rownum") == 0) { context.rownumVar = (Var *)entry->expr; } } /* && rownumVar != NULL */ Assert (context.levelVar != NULL); /* Step 2. Apply fake const replacement walker to the expression */ ReplaceFakeConstWalker(connectByLevelExpr, &context); /* Step 3. Do reqular const expression process */ swoptions->connect_by_level_quals = eval_const_expressions(root, connectByLevelExpr); } /* * - brief: Do possible post-path stage optimization, e.g. add Material node on an * NestLoop's outer part * * - Note: Ideally should be handled in PathNode generation stage, in order to do so we have * serveral case need to come up with e.g. identfy which SubQuery's output should be * materilzed, for short term we only to improve to avoid the worst case where * RuPlan's inner branch ReScan()-ed many times and its BaseRel with high selectivity */ static void OptimizeStartWithPlan(PlannerInfo *root, StartWithOp *swplan) { RecursiveUnion *ruplan = swplan->ruplan; /* shunking only supported scenarios */ if (!IsA(innerPlan(ruplan), SubqueryScan)) { return; } SubqueryScan *sqscan = (SubqueryScan *)innerPlan(ruplan); Plan *joinPlan = sqscan->subplan; /* only handle nestloop and hash join case */ if (!IsA(joinPlan, NestLoop) && !IsA(joinPlan, HashJoin)) { return; } /* add materialize */ Plan *joinOuter = outerPlan(joinPlan); Plan *joinInner = innerPlan(joinPlan); Plan *wtscan = NULL; /* * Only handle WorkTableScan is in inner case, when WorkTableScan is in outer tree it * can not be material-optimized as its result is volatile for each iteration. */ if (IsA(joinPlan, NestLoop) && IsA(joinInner, WorkTableScan)) { wtscan = joinInner; } else if (IsA(joinPlan, HashJoin) && IsA(joinInner, Hash) && IsA(joinInner->lefttree, WorkTableScan)) { wtscan = joinInner->lefttree; } else { /* unrecognized cases */ return; } Assert (wtscan != NULL && IsA(wtscan, WorkTableScan)); if (!NeedMaterialJoinOuter(joinOuter, wtscan)) { return; } Material *mtplan = make_material(joinOuter, true); inherit_plan_locator_info((Plan *)mtplan, joinOuter); copy_plan_costsize((Plan *)mtplan, joinOuter); joinPlan->lefttree = (Plan *)mtplan; } /* * ------------------------------------------------------------------------------------- * @Brief: The major processing logic entry point for start-with, given a CteScan node, * we insert a StartWithOp proc node to do START WITH .... CONNECT BY processing, * normally the process is separate many steps, see details as below: * * step(1): Create StartWithOp node and insert it between CtScan and RuScan, also replace * the subplan refs in current PlannerInfo level * step(2): Process ConnectByLevel/Rownum's fake const value * step(3): Add possible post-path generation plan optimization e.g. rescan-avoid for * start with * step(4): Create Internal target list (RUITR/array_key/array_columns) * step(5): Specially process ORDER SIBLINGS BY case * ------------------------------------------------------------------------------------- */ Plan *AddStartWithOpProcNode(PlannerInfo *root, CteScan *cteplan, RecursiveUnion *ruplan) { Plan *plan = (Plan *)cteplan; StartWithOp *swplan = NULL; /* * Step1. Create a StartWithOp node between CteScan and RuScan, also update * SubPlan-ref in curernt PlannerInfo's glob->subplans */ swplan = CreateStartWithOpNode(root, cteplan, ruplan); /* * Step2. In case of connect by level/rownum, we need create new quals to evaluate * level/rownum condition in StartWith operator */ ProcessConnectByFakeConst(root, swplan); /* * Step3. Do some possible post-path generation plan optimization, to avoid worst * cases, e.g. recursive part with high selectivity but rescan many times */ OptimizeStartWithPlan(root, swplan); /* * Step4. Add internal TargetEntryList to CteScan & RuScan, also add some runtime level * optimization hints, e.g. swSkipOptions */ BuildStartWithInternalTargetList(root, cteplan, swplan); /* * Step5. Process order-siblings cases */ ProcessOrderSiblings(root, swplan); /* Copy Params from ruplan */ swplan->plan.extParam = bms_copy(ruplan->plan.extParam); swplan->plan.allParam = bms_copy(ruplan->plan.allParam); return plan; } /* * Local Routines to support AddStartWithOpProcNode() */ static StartWithOp* CreateStartWithOpNode(PlannerInfo *root, CteScan *cteplan, RecursiveUnion *ruplan) { Plan *plan = (Plan *)cteplan; StartWithOp *swplan = NULL; Index relid = cteplan->scan.scanrelid; int natts = 0; RangeTblEntry *rte = NULL; /* 1. fix targetlist with explict name */ natts = list_length(plan->targetlist); rte = rt_fetch(relid, root->parse->rtable); plan->targetlist = FixSwTargetlistResname(root, rte, plan->targetlist); /* 2. Build StartWithOp's targetlist */ swplan = makeNode(StartWithOp); swplan->plan.lefttree = (Plan *)ruplan; swplan->plan.targetlist = (List *)copyObject(plan->targetlist); swplan->ruplan = ruplan; swplan->cteplan = cteplan; swplan->swoptions = (StartWithOptions *)copyObject(cteplan->cteRef->swoptions); /* generate PRC list for both swplan and ctescan */ swplan->prcTargetEntryList = GetPRCTargetEntryList(root, rte, swplan); cteplan->prcTargetEntryList = (List *)copyObject(swplan->prcTargetEntryList); /* Inherit ctescan's targetlist to StartWithOp */ inherit_plan_locator_info((Plan *)swplan, plan); copy_plan_costsize((Plan *)swplan, (Plan *)ruplan); /* * Initialize PRC runtime optimization hints. * * Basically, we do optimistic scheme assuming all PRC on CTE is able to skip */ { SetColumnSkipOptions(swplan->swExecOptions); /* Mark level/rownum is not skipped, as they are lit-enough */ ClearSkipLevel(swplan->swExecOptions); SetSkipIsLeaf(swplan->swExecOptions); SetSkipIsCycle(swplan->swExecOptions); ClearSkipRownum(swplan->swExecOptions); } /* fix original Ruplan refered place */ cteplan->subplan = (RecursiveUnion *)swplan; List *newSubplans = NIL; ListCell *lc = NULL; foreach (lc, root->glob->subplans) { Plan *plan = (Plan *)lfirst(lc); if (IsA(plan, RecursiveUnion) && (Plan *)plan == (Plan *)ruplan) { plan = (Plan *)swplan; } newSubplans = lappend(newSubplans, plan); } root->glob->subplans = newSubplans; return swplan; } static void ProcessConnectByFakeConst(PlannerInfo *root, StartWithOp *swplan) { if (!IsConnectByLevelStartWithPlan(swplan)) { return; } StartWithOptions *swoptions = swplan->swoptions; /* fix fake const in qual expr */ ReplaceFakeConst(root, swplan); /* do preprocess for connect-by-level/rownum condition */ ((Plan *)swplan)->qual = (List *)preprocess_expression(root, swoptions->connect_by_level_quals, EXPRKIND_QUAL); } static void BuildStartWithInternalTargetList(PlannerInfo *root, CteScan *cteplan, StartWithOp *swplan) { List *keyEntryList = NIL; List *colEntryList = NIL; bool needSiblings = (swplan->swoptions->siblings_orderby_clause != NULL) ? true : false; /* Generate internal target entry keyEntryList & colEntryList */ GenerateStartWithInternalEntries(root, cteplan, &keyEntryList, &colEntryList); if (keyEntryList == NULL) { ereport(DEBUG1, (errmodule(MOD_OPT_PLANNER), errmsg("keyEntryList is NULL, query:%s", t_thrd.postgres_cxt.debug_query_string))); } if (colEntryList == NULL) { ereport(DEBUG1, (errmodule(MOD_OPT_PLANNER), errmsg("colEntryList is NULL, query:%s", t_thrd.postgres_cxt.debug_query_string))); } /* Add internal key/col entry list and pseudo_list for StartWithOp node */ swplan->internalEntryList = BuildStartWithPlanPseudoTargetList((Plan *)swplan, cteplan->scan.scanrelid, keyEntryList, colEntryList, needSiblings); /* Add internal key/col entry list for CteScan node */ cteplan->internalEntryList = BuildStartWithPlanPseudoTargetList((Plan *)cteplan, cteplan->scan.scanrelid, keyEntryList, colEntryList, needSiblings); /* construct fullEntryList (RUITR + array_key + array_col ) */ List *fullEntryList = NIL; ListCell *entry = NULL; foreach (entry, cteplan->scan.plan.targetlist) { TargetEntry *te = (TargetEntry *)lfirst(entry); /* skip regular columns */ if (IsPseudoReturnTargetEntry(te) || IsPseudoInternalTargetEntry(te)) { fullEntryList = lappend(fullEntryList, copyObject(te)); } } swplan->keyEntryList = keyEntryList; swplan->colEntryList = colEntryList; swplan->fullEntryList = fullEntryList; } static void ProcessOrderSiblings(PlannerInfo *root, StartWithOp *swplan) { CteScan *cteplan = swplan->cteplan; RecursiveUnion *ruplan = swplan->ruplan; StartWithOptions *swoptions = swplan->swoptions; if (!StartWithNeedSiblingSort(root, cteplan)) { return; } /* Fix RecursiveUnion TargetList */ ListCell *lc = NULL; foreach(lc, cteplan->scan.plan.targetlist) { TargetEntry *te = (TargetEntry *)lfirst(lc); if (IsPseudoReturnColumn(te->resname)) { ruplan->plan.targetlist = lappend(ruplan->plan.targetlist, te); } } ruplan->internalEntryList = BuildStartWithPlanPseudoTargetList((Plan *)ruplan, cteplan->scan.scanrelid, swplan->keyEntryList, swplan->colEntryList, true); /* 1. Add under RU sort plan */ ruplan->plan.lefttree = (Plan *)CreateSortPlanUnderRU(root, ruplan->plan.lefttree, swoptions->siblings_orderby_clause, -1); ruplan->plan.righttree = (Plan *)CreateSortPlanUnderRU(root, ruplan->plan.righttree, swoptions->siblings_orderby_clause, -1); } /* * -------------------------------------------------------------------------------------- * LOCAL functions * -------------------------------------------------------------------------------------- */ /* * @Brief: build full pseudo entries for given pro node, with input a keylist and collist * * pseudo target list * [1]. RUIRT * [2]. array_key_1 * [3]. array_col_2 * [4]. array_col_3 * [6].... * * example: * Table: * t1(id, name, fatherid, name_desc) * Query: * select *, * level, connect_by_isleaf, connect_is_cycle, * connect_by_root name_desc, sys_connect_by_path(name, '@') * from t1 * start with name = 'shanghai' * connect by id = PRIOR fatherid; * * pseudo-targetlist: array [ * - RUTI(type:int) * - array_key_1(type:text) * - array_col_2(type:text) * - array_col_4(type:text) * ] */ static List* BuildStartWithPlanPseudoTargetList(Plan *plan, Index varno, List *key_list, List *col_list, bool needSiblings) { Node *expr = NULL; TargetEntry *te = NULL; TargetEntry *pte = NULL; List *internalEntryList = NIL; int rc = 0; /* we only have to handle CteScan & RecursiveUnion node */ Assert (IsA(plan, CteScan) || IsA(plan, RecursiveUnion) || IsA(plan, StartWithOp)); /* 1. Add "RUITR" entry to support LEVEL */ expr = (Node *)makeVar(varno, list_length(plan->targetlist) + 1, INT4OID, -1, InvalidOid, 0); pte = makeTargetEntry((Expr *)expr, list_length(plan->targetlist) + 1, "RUITR", false); plan->targetlist = lappend(plan->targetlist, pte); internalEntryList = lappend(internalEntryList, pte); /* * 2. Add "array_key" entry we have to add to support connect_by_isleaf, connect_by_iscycle, * order-siblings */ ListCell *lc = NULL; foreach (lc, key_list) { te = (TargetEntry *)lfirst(lc); char resname[NAMEDATALEN] = {0}; rc = sprintf_s(resname, NAMEDATALEN, "array_key_%d", te->resno); securec_check_ss(rc, "\0", "\0"); expr = (Node *)makeVar(varno, list_length(plan->targetlist) + 1, TEXTOID, -1, TEXT_COLLCATION, 0); pte = makeTargetEntry((Expr *)expr, list_length(plan->targetlist) + 1, pstrdup(resname), false); plan->targetlist = lappend(plan->targetlist, pte); internalEntryList = lappend(internalEntryList, pte); } /* * 3. Add "array_col" to support , connect_by_root */ foreach (lc, col_list) { te = (TargetEntry *)lfirst(lc); char resname[NAMEDATALEN] = {0}; rc = sprintf_s(resname, NAMEDATALEN, "array_col_%d", te->resno); securec_check_ss(rc, "\0", "\0"); expr = (Node *)makeVar(varno, list_length(plan->targetlist) + 1, TEXTOID, -1, TEXT_COLLCATION, 0); pte = makeTargetEntry((Expr *)expr, list_length(plan->targetlist) + 1, pstrdup(resname), false); plan->targetlist = lappend(plan->targetlist, pte); internalEntryList = lappend(internalEntryList, pte); } /* * Add "array_siblings" pseudo columns support if need */ if (needSiblings) { expr = (Node *)makeVar(varno, list_length(plan->targetlist) + 1, BYTEAOID, -1, 0, 0); pte = makeTargetEntry((Expr *)expr, list_length(plan->targetlist) + 1, "array_siblings", false); plan->targetlist = lappend(plan->targetlist, pte); internalEntryList = lappend(internalEntryList, pte); } return internalEntryList; } /* * @brief: check if a given connect by function is valid for its args, currently only * connect_by_root()/sys_connect_by_path() is supported */ static void CheckInvalidConnectByfuncArgs(CteScan *cteplan, Oid funcid, List *arg_vars) { /* for none connect by function we do not do arg checks on its type */ if (funcid != CONNECT_BY_ROOT_FUNCOID && funcid != SYS_CONNECT_BY_PATH_FUNCOID) { return; } int maxBaseRelAttnum = list_length(cteplan->scan.plan.targetlist) - STARTWITH_PSEUDO_RETURN_ATTNUMS; /* validate common cases */ if (list_length(arg_vars) > 1) { elog(ERROR, "only single column can be put as argument in connect_by_root/sys_connect_by_path()"); } /* * For compatible with Oracle where sys_connect_by_path() and connect_by_root() * allow a const value set as 1st paramameter, return anyway */ if (arg_vars == NIL) { return; } Var *arg = (Var *)linitial(arg_vars); if (arg->varattno > maxBaseRelAttnum) { elog(ERROR, "only base table column can be specified in connect_by_root/sys_connect_by_path()"); } /* valid specific cases */ switch (funcid) { case CONNECT_BY_ROOT_FUNCOID: { /* for connect_by_root() we only check if base column */ break; } case SYS_CONNECT_BY_PATH_FUNCOID: { /* for sys_connect_by_path() allow textual data types TEXT/VARCHAR/CHAR */ Oid typid = arg->vartype; if (typid != TEXTOID && typid != VARCHAROID && typid != BPCHAROID && typid != NVARCHAR2OID) { elog(ERROR, "only text type(CHAR/VARCHAR/NVARCHAR2/TEXT) is allow for sys_connect_by_path()"); } break; } default: { elog(ERROR, "unknown functions funid:%u in ConnectByFuncArg check.", funcid); } } } static bool PullUpConnectByFuncExprWalker(Node *node, PullUpConnectByFuncExprContext *context) { if (node == NULL) { return false; } /* FuncExpr we do connect-by func validation check */ if (IsA(node, FuncExpr)) { FuncExpr *func = (FuncExpr *)node; /* Only handle start with hierachocal query's function cases */ if (IsHierarchicalQueryFuncOid(func->funcid)) { context->pullupFuncs = list_append_unique(context->pullupFuncs, func); } } return expression_tree_walker(node, (bool (*)())PullUpConnectByFuncExprWalker, (void *)context); } List *pullUpConnectByFuncExprs(Node* node) { errno_t rc = 0; PullUpConnectByFuncExprContext context; rc = memset_s(&context, sizeof(PullUpConnectByFuncExprContext), 0, sizeof(PullUpConnectByFuncExprContext)); securec_check(rc, "\0", "\0"); (void)PullUpConnectByFuncExprWalker(node, &context); return context.pullupFuncs; } /* * -------------------------------------------------------------------------------------- * @Brief: Functions to support var pull up from subquery's target list, also check each * possible vars, so that we can do possible PRC skip optimizations * * PullUpConnectByFuncVars() * -------------------------------------------------------------------------------------- */ static List *PullUpConnectByFuncVars(PlannerInfo *root, CteScan *cteScan, Node *targetEntry) { errno_t rc = 0; PullUpConnectByFuncVarContext context; rc = memset_s(&context, sizeof(PullUpConnectByFuncVarContext), 0, sizeof(PullUpConnectByFuncVarContext)); securec_check(rc, "\0", "\0"); context.root = root; context.pullupVars = NIL; context.cteplan = cteScan; context.swplan = (StartWithOp *)cteScan->subplan; (void)PullUpConnectByFuncVarsWalker(targetEntry, &context); return context.pullupVars; } static bool PullUpConnectByFuncVarsWalker(Node *node, PullUpConnectByFuncVarContext *context) { if (node == NULL) { return false; } /* * We handle two cases here: * 1. FuncExpr we do connect-by func validation check * 2. Var, we check if its PRC and possible PRC-skip optimization could apply */ if (IsA(node, FuncExpr)) { FuncExpr *func = (FuncExpr *)node; /* Only handle start with hierachocal query's function cases */ if (IsHierarchicalQueryFuncOid(func->funcid)) { /* pull-up basic vars from FuncExpr node */ List *vars = pull_var_clause((Node*)func->args, PVC_RECURSE_AGGREGATES, PVC_INCLUDE_PLACEHOLDERS); CheckInvalidConnectByfuncArgs(context->cteplan, func->funcid, vars); context->pullupVars = list_concat_unique(context->pullupVars, vars); } } else if (IsA(node, Var)) { /* check if there is target var refer to PRC and mark them not-skipable */ Var *var = (Var *)node; int prcType = GetVarPRCType(context->cteplan->prcTargetEntryList, var); if (prcType == SWCOL_LEVEL || prcType == SWCOL_ISLEAF || prcType == SWCOL_ISCYCLE || prcType == SWCOL_ROWNUM) { MarkPRCNotSkip(context->swplan, prcType); } } return expression_tree_walker(node, (bool (*)())PullUpConnectByFuncVarsWalker, (void*)context); } /* * -------------------------------------------------------------------------------------- * @Brief: Functions to relevant to PRC optimization in hierachical queries * - GetPRCTargetEntryList() * - GetVarPRCType() * - MarkPRCNotSkip() * -------------------------------------------------------------------------------------- */ static List *GetPRCTargetEntryList(PlannerInfo *root, RangeTblEntry *rte, StartWithOp *swplan) { int natts = 0; int baseColNum = 0; Plan *plan = (Plan *)swplan; List *prcTargetEntryList = NIL; /* check if PRC targetlist is already generated */ if (swplan->prcTargetEntryList != NIL) { return swplan->prcTargetEntryList; } baseColNum = list_length(rte->eref->colnames) - STARTWITH_PSEUDO_RETURN_ATTNUMS; natts = list_length(plan->targetlist); if (natts - baseColNum != STARTWITH_PSEUDO_RETURN_ATTNUMS && baseColNum != natts) { elog(ERROR, "unrecognized case baseColNum/tlist"); } /* attach resname for target entry */ ListCell *lc = NULL; foreach (lc, plan->targetlist) { TargetEntry *entry = (TargetEntry *)lfirst(lc); if (entry->resno > baseColNum) { prcTargetEntryList = lappend(prcTargetEntryList, entry); } } Assert (prcTargetEntryList != NIL); return prcTargetEntryList; } static int GetVarPRCType(List *prcList, const Var* var) { ListCell *lc = NULL; int prcType = SWCOL_LEVEL; foreach (lc, prcList) { TargetEntry *tle = (TargetEntry *)lfirst(lc); if (!equal(var, tle->expr)) { prcType++; continue; } break; } /* none-prc column, we consider its type as unknown */ if (prcType != SWCOL_LEVEL && prcType != SWCOL_ISLEAF && prcType != SWCOL_ISCYCLE && prcType != SWCOL_ROWNUM) { prcType = SWCOL_UNKNOWN; } return prcType; } static void MarkPRCNotSkip(StartWithOp *swplan, int prcType) { if (prcType == SWCOL_LEVEL || prcType == SWCOL_ROWNUM) { return; } switch (prcType) { case SWCOL_ISLEAF: { ClearSkipIsLeaf(swplan->swExecOptions); break; } case SWCOL_ISCYCLE: { ClearSkipIsCycle(swplan->swExecOptions); break; } default: { /* do nothing */ } } return; } /* * -------------------------------------------------------------------------------------- * @Brief: Generate internal entries for given CteScan node, here the term internal * entry of key-value arraies prfixed with "array_key_nn" & col-value arraies * prefixed with "array_col_mm" and store them in separate output list parm * * @param: * - cteplan: the target CtePlan node where we generate key/col array * - keyEntryList: A ** pointer to accept pseudo key entry * - colEntryList: A ** pointer to accept pseudo col entry * * @Return: void, return value are set in keyEntryList & colEntryList * -------------------------------------------------------------------------------------- */ static void GenerateStartWithInternalEntries(PlannerInfo *root, CteScan *cteplan, List **keyEntryList, List **colEntryList) { Assert (IsA(cteplan, CteScan) && IsA(cteplan->subplan, StartWithOp)); Plan *plan = (Plan *)cteplan; StartWithOp *swplan = (StartWithOp *)cteplan->subplan; ListCell *lc = NULL; List *tmp_list = NULL; /* * First, match cte targetEntry in funcs like connect_by_root(xxx) and * SYS_CONNECT_BY_PATH(xxx, '/') */ foreach(lc, root->parse->targetList) { TargetEntry *tle = (TargetEntry *)lfirst(lc); List *vars = PullUpConnectByFuncVars(root, cteplan, (Node *)tle); tmp_list = list_concat(tmp_list, vars); } /* process those specified in where clause */ foreach(lc, plan->qual) { TargetEntry *origin = (TargetEntry *)lfirst(lc); List *vars = PullUpConnectByFuncVars(root, cteplan, (Node *)origin); tmp_list = list_concat(tmp_list, vars); } foreach (lc, plan->targetlist) { TargetEntry *te = (TargetEntry *)lfirst(lc); Assert (IsA(te->expr, Var)); if (list_member(tmp_list, te->expr)) { *colEntryList = lappend(*colEntryList, te); } } /* * Second, match cte targetEntry of connectby prior columns, like prior pid = id then pid is * key */ CommonTableExpr *cteRef = cteplan->cteRef; foreach(lc, cteRef->swoptions->prior_key_index) { int index = lfirst_int(lc); TargetEntry *te = (TargetEntry *)list_nth(plan->targetlist, index); *keyEntryList = lappend(*keyEntryList, te); } /* do not skip iscycle PRC if "nocycle" is specified in HQ clause */ if (swplan->swoptions->nocycle) { ClearSkipIsCycle(swplan->swExecOptions); } return; } /* * @Brief: Add sort operator on top of CteScan with column with sort key * {"LEVEL", "sibling_order_columns"} */ static OrderSiblingSortEntry* CreateOrderSiblingSortEntry(TargetEntry *entry, SortByDir dir) { OrderSiblingSortEntry *sortEntry = (OrderSiblingSortEntry *)palloc0(sizeof(OrderSiblingSortEntry)); sortEntry->tle = entry; switch (dir) { case SORTBY_DEFAULT: case SORTBY_ASC: { sortEntry->sortCmpOp[SORTCMP_LT] = true; sortEntry->sortCmpOp[SORTCMP_EQ] = true; sortEntry->sortCmpOp[SORTCMP_GT] = false; break; } case SORTBY_DESC: { sortEntry->sortCmpOp[SORTCMP_LT] = false; sortEntry->sortCmpOp[SORTCMP_EQ] = false; sortEntry->sortCmpOp[SORTCMP_GT] = true; break; } default: { /* for default case we treat as ASC */ sortEntry->sortCmpOp[SORTCMP_LT] = true; sortEntry->sortCmpOp[SORTCMP_EQ] = false; sortEntry->sortCmpOp[SORTCMP_GT] = false; } } return sortEntry; } static Sort *CreateSiblingsSortPlan(PlannerInfo* root, Plan* lefttree, List *sortEntryList, double limit_tuples) { Sort *sort = NULL; int numsortkeys = list_length(sortEntryList); AttrNumber* sortColIdx = (AttrNumber*)palloc(numsortkeys * sizeof(AttrNumber)); Oid* sortOperators = (Oid*)palloc(numsortkeys * sizeof(Oid)); Oid* collations = (Oid*)palloc(numsortkeys * sizeof(Oid)); bool* nullsFirst = (bool*)palloc(numsortkeys * sizeof(bool)); numsortkeys = 0; Oid sortoplt = InvalidOid; Oid sortopeq = InvalidOid; Oid sortopgt = InvalidOid; bool hashable = false; ListCell* l = NULL; foreach (l, sortEntryList) { OrderSiblingSortEntry *entry = (OrderSiblingSortEntry *)lfirst(l); get_sort_group_operators(exprType((Node*)entry->tle->expr), entry->sortCmpOp[SORTCMP_LT], entry->sortCmpOp[SORTCMP_EQ], entry->sortCmpOp[SORTCMP_GT], &sortoplt, &sortopeq, &sortopgt, &hashable); Oid sortop = InvalidOid; if (entry->sortCmpOp[SORTCMP_LT]) { sortop = sortoplt; } else if (entry->sortCmpOp[SORTCMP_GT]) { sortop = sortopgt; } else { /* we shouldn't get here */ sortop = sortopeq; } sortColIdx[numsortkeys] = entry->tle->resno; sortOperators[numsortkeys] = sortop; collations[numsortkeys] = exprCollation((Node*)entry->tle->expr); nullsFirst[numsortkeys] = entry->sortByNullsFirst; numsortkeys++; } sort = make_sort(root, lefttree, numsortkeys, sortColIdx, sortOperators, collations, nullsFirst, limit_tuples); return sort; } static bool IsNullsFirst(SortBy *sortby) { Assert(sortby != NULL); bool ret = false; bool reverse = (sortby->sortby_dir == SORTBY_DESC); switch (sortby->sortby_nulls) { case SORTBY_NULLS_DEFAULT: /* NULLS FIRST is default for DESC; other way for ASC */ ret = reverse; break; case SORTBY_NULLS_FIRST: ret = true; break; case SORTBY_NULLS_LAST: ret = false; break; default: ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NODE_STATE), errmsg("unrecognized sortby_nulls: %d", sortby->sortby_nulls))); break; } return ret; } /* * @Brief: Generate sort key under recursive union according siblings sort key * select * from t1 start with...connect by...order siblings by c1,c2,c3 * the plan like this: * ------------------QUERY PLAN-------------------- * > StartWithOP * > Recursive Union * > Sort Key, sort by c1, c2, c3 <<<<< we are have * > Seqscan * > Sort Key, sort by c1, c2, c3 <<<<< we are have * > Seqscan */ static Sort *CreateSortPlanUnderRU(PlannerInfo* root, Plan* lefttree, List *siblings, double limit_tuples) { Sort *sort = NULL; List *sortEntryList = NIL; ListCell *lc = NULL; ListCell *lc1 = NULL; foreach (lc, siblings) { SortBy *sb = (SortBy *)lfirst(lc); char *colname = GetOrderSiblingsColName(root, sb, lefttree); if (colname == NULL) { ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Order siblings by clause has unexpected key."))); continue; } bool found = false; /* search targetlist */ foreach (lc1, lefttree->targetlist) { TargetEntry *tle = (TargetEntry *)lfirst(lc1); if (tle->resname == NULL) { continue; } /* one more fix name */ char *label = strrchr(tle->resname, '@'); label += 1; label = pstrdup(label); if (pg_strcasecmp(label, colname) == 0) { found = true; OrderSiblingSortEntry *entry = CreateOrderSiblingSortEntry(tle,sb->sortby_dir); entry->sortByNullsFirst = IsNullsFirst(sb); sortEntryList = lappend(sortEntryList, entry); if (u_sess->attr.attr_sql.enable_startwith_debug) { elog(WARNING, "Good we got siblings sort key under RU."); } } } if (!found) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmodule(MOD_OPT), errmsg("Siblings sort entry not found"), errdetail("Column %s not found or not allowed here", colname), errcause("Incorrect query input"), erraction("Please check and revise your query"))); } } if (sortEntryList == NIL) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmodule(MOD_OPT), errmsg("Invalid order siblings by clause"), errdetail("Siblings sort entry not found"), errcause("Incorrect query input"), erraction("Please check and revise your query"))); } sort = CreateSiblingsSortPlan(root, lefttree, sortEntryList, limit_tuples); /* Free sort entries */ list_free_deep(sortEntryList); return sort; } /* * -------------------------------------------------------------------------------------- * fix WorkTableScan's targetlist * -------------------------------------------------------------------------------------- */ /* * @Brief: push down all pseudo columns into plan node access (start from topNode * end to botNode) */ void PushDownFullPseudoTargetlist(PlannerInfo *root, Plan *topNode, Plan *botNode, List *fullEntryList) { Assert (fullEntryList != NIL); PlanAccessPathSearchContext ctx; errno_t rc = EOK; rc = memset_s(&ctx, sizeof(PlanAccessPathSearchContext), 0, sizeof(PlanAccessPathSearchContext)); securec_check(rc, "\0", "\0"); ctx.topNode = topNode; ctx.botNode = botNode; ctx.fullEntryList = fullEntryList; for (int i = 0; i < MAX_PLAN_DEPTH; i++) { ctx.planStack.value_array[i] = NULL; ctx.varnoStack.value_array[i] = 0; } ctx.planStack.top = -1; ctx.varnoStack.top = -1; ctx.done = false; ctx.numsPrevTlist = -1; /* * Get the plan search path and add pseudo entry to let pseudo content return * along with executor iteration normally. */ GetWorkTableScanPlanPath(root, (Plan *)topNode, &ctx); Assert (ctx.planStack.top == ctx.varnoStack.top); int nodeNums = ctx.planStack.top + 1; if (nodeNums == 0) { return; } StringInfoData si; initStringInfo(&si); /* Iterate the plan search path and add pseudo entry properly */ Plan *plan = NULL; Index varno = 0; while (nodeNums > 0) { plan = StackNodePop(&ctx.planStack); varno = StackVarnoPop(&ctx.varnoStack); Assert (plan != NULL && varno > 0); /* bind current node with proper pseudo entry info */ (void)BindPlanNodePseudoEntries(root, plan, varno, &ctx); /* record current numbers of tlist */ ctx.numsPrevTlist = list_length(plan->targetlist); /* build debug path string */ appendStringInfo(&si, " -> %s(tlist_len:%d)", nodeTagToString(nodeTag(plan)), list_length(plan->targetlist)); nodeNums--; } if (u_sess->attr.attr_sql.enable_startwith_debug) { elog(WARNING, "Pushdown pseudo_tlist >>>>> [%s]", si.data); } pfree_ext(si.data); return; } /* * -------------------------------------------------------------------------------------- * @Brief: Fix Array Internal Entry based on current targetlist level * -------------------------------------------------------------------------------------- */ static void FixArrayInternalEntry(List *targetlsit) { ListCell *lc1 = NULL; ListCell *lc2 = NULL; foreach(lc1, targetlsit) { TargetEntry *entry = (TargetEntry *)lfirst(lc1); if (entry->resname != NULL && (strstr(entry->resname, "array_key_") || strstr(entry->resname, "array_col_"))) { int varno = ((Var *)entry->expr)->varno; int attno = entry->resname[10] - '0'; int resno = 0; foreach(lc2, targetlsit) { TargetEntry *entry2 = (TargetEntry *)lfirst(lc2); if (GetPseudoColumnType(entry2) == SWCOL_REGULAR) { int varno2 = ((Var *)entry2->expr)->varno; int attno2 = ((Var *)entry2->expr)->varattno; if (varno == varno2 && attno2 == attno) { resno = entry2->resno; break; } } } char newArrayName[NAMEDATALEN]; errno_t rc = memset_s(newArrayName, NAMEDATALEN, 0, NAMEDATALEN); securec_check(rc, "\0", "\0"); if (strstr(entry->resname, "array_key_")) { rc = sprintf_s(newArrayName, NAMEDATALEN, "array_key_%d", resno); securec_check_ss(rc, "\0", "\0"); } else if (strstr(entry->resname, "array_col_")) { rc = sprintf_s(newArrayName, NAMEDATALEN, "array_col_%d", resno); securec_check_ss(rc, "\0", "\0"); } entry->resname = pstrdup(newArrayName); } } return; } /* * -------------------------------------------------------------------------------------- * @Brief: Determine if a given pseudo target entry exits in given targetlist, the term * "exists" says with identical varno/varattno * -------------------------------------------------------------------------------------- */ static bool IsPseudoInternalEntryExists(List *targetlist, TargetEntry *tle) { bool result = false; ListCell *lc = NULL; Assert (tle != NULL); /* * Exist emtry targetlist during start with...connect by push down pseudo columns. * e.x * Select * from (select t1.c1 as num from t1 join t2 on true) as ss, t3 * start with ss.num = t3.c2 * connect by prior t3.c1 = t3.c3; * * In this case, if we have plan like * Nestloop1 --> t1 * --> NestLoop2 --> t2 * --> worktable * * Then the NestLoop2 don't have any targetlist, but we also should * inherit targetlist from wortable scan. * */ if (targetlist == NULL) { return result; } foreach (lc, targetlist) { TargetEntry *curEntry = (TargetEntry *)lfirst(lc); if (GetPseudoColumnType(curEntry) == SWCOL_REGULAR) { /* * skip regular entry, we only check special entries e.g. pseodu return column, * internal entry */ continue; } Assert (IsA(tle->expr, Var)); if (pg_strcasecmp(curEntry->resname, tle->resname) == 0) { result = true; break; } } return result; } /* * -------------------------------------------------------------------------------------- * @brief: Add each pseudo entry to arget plan node * -------------------------------------------------------------------------------------- */ static void AddPseudoEntries(Plan *plan, Index relid, PlanAccessPathSearchContext *context) { ListCell *lc = NULL; List *fullEntryList = context->fullEntryList; Assert (plan != NULL && relid > 0 && fullEntryList != NIL); int index = list_length(plan->targetlist); int attno = 0; bool adapt = false; /* caculate PseudoColumn varattno from prev plan targetlist */ if (context->numsPrevTlist != -1) { attno = context->numsPrevTlist - list_length(fullEntryList); adapt = true; } /* Only add entry that does not added before */ foreach (lc, fullEntryList) { TargetEntry *entry = (TargetEntry *)copyObject((TargetEntry *)lfirst(lc)); /* * In "pseudo targetlist" push down process, we are going to avoid adding * duplicate entry we do such kind of check, not-exit puls resno is appended * at tail */ if (!IsPseudoInternalEntryExists(plan->targetlist, entry)) { /* * The target entry's resno,varno,varattno is created in CteScan planing stage, * before we add it to curernt plan, we need do proper resno adjustment */ Assert (IsA(entry->expr, Var)); index++; ((Var *)entry->expr)->varno = relid; if (adapt) { attno++; ((Var *)entry->expr)->varattno = attno; } entry->resno = index; plan->targetlist = lappend(plan->targetlist, entry); } } if (context->botNode != NULL) { FixArrayInternalEntry(plan->targetlist); } return; } /* * -------------------------------------------------------------------------------------- * @brief: Add pseudo entries to arget plan node * -------------------------------------------------------------------------------------- */ static void BindPlanNodePseudoEntries(PlannerInfo *root, Plan *plan, Index varno, PlanAccessPathSearchContext *context) { ListCell *lc = NULL; switch (nodeTag(plan)) { case T_RecursiveUnion: { /* * for recursive-union, besides do proper pseudo entry adding, we need build * a separate list to help tupleslot-conversion(RuScan->StartWithOp) */ RecursiveUnion *ruplan = (RecursiveUnion *)plan; /* * Already add pseudo entry to RecursiveUnion once order siblings by exist, * so we don't need add pseudo entry again. */ if (ruplan->internalEntryList != NULL) { break; } AddPseudoEntries(plan, varno, context); foreach (lc, context->fullEntryList) { TargetEntry *entry = (TargetEntry *)copyObject((TargetEntry *)lfirst(lc)); if (IsPseudoInternalTargetEntry(entry)) { ruplan->internalEntryList = lappend(ruplan->internalEntryList, entry); } } break; } /* process regular case */ case T_Hash: case T_HashJoin: case T_SubqueryScan: case T_CteScan: case T_NestLoop: case T_MergeJoin: case T_Sort: case T_Agg: case T_BaseResult: case T_Material: case T_Append: case T_Limit: case T_Unique: case T_WindowAgg: case T_WorkTableScan: { AddPseudoEntries(plan, varno, context); break; } default: { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmodule(MOD_OPT), errmsg("%s is not supported in start with / connect by clauses", nodeTagToString(nodeTag(plan))), errdetail("%s is not suppoted", nodeTagToString(nodeTag(plan))), errcause("Input unsupported feature"), erraction("NA"))); } } } /* * -------------------------------------------------------------------------------------- * @Brief: Generate a minimal set of plan-path reaching WTScan and to add pseudo entry, * take example as follow: the generate path: * RecursiveUnion -> HashJoin -> Hash -> WorkTableScan * * CteScan * / * StartWithOp * / * RecursiveUnion >>> CONVERT TO >>>>>>> RecursiveUnion * / \ \ * SeqScan HashJoin HashJoin * / \ \ * SeqScan Hash Hash * / / * WorktableScan <<<< target WorkTableScan * -------------------------------------------------------------------------------------- */ static void GetWorkTableScanPlanPath(PlannerInfo *root, Plan *node, PlanAccessPathSearchContext *context) { StackNode *planStack = &context->planStack; StackVarno *varnoStack = &context->varnoStack; if (node == NULL) { return; } if (context->done) { return; } /* enqueue() */ StackNodePush(planStack, node); switch (nodeTag(node)) { case T_CteScan: { CteScan *ctescan = (CteScan *)node; if ((Node *)context->botNode == (Node *)ctescan) { context->done = true; Assert (context->relid == 0); context->relid = ctescan->scan.scanrelid; StackVarnoPush(varnoStack, ctescan->scan.scanrelid); } } break; case T_WorkTableScan: { WorkTableScan *wtscan = (WorkTableScan *)node; /* mark finish and to exit the iteration call stack immediately */ if (context->botNode == NULL) { context->done = true; /* set relid in current subquery level */ Assert (context->relid == 0); context->relid = wtscan->scan.scanrelid; StackVarnoPush(varnoStack, wtscan->scan.scanrelid); } } break; case T_VecSubqueryScan: case T_SubqueryScan: { SubqueryScan *sq = (SubqueryScan *)node; StackVarnoPush(varnoStack, sq->scan.scanrelid); GetWorkTableScanPlanPath(root, sq->subplan, context); if (!context->done) { StackVarnoPop(varnoStack); } } break; default: { Plan *lplan = outerPlan(node); Plan *rplan = innerPlan(node); /* * We need handle a case where RecursiveUnion's inner branch is BaseResult * node, normally this happens a connectByExpr is evaluate as FALSE, instad * of WT-join a BaseResult node is planned, e.g. * e.g. * [QUERY] * SELECT * FROM test_hcb_ptb t1 * START WITH id=141 * CONNECT BY (prior pid)=id and prior pid>10 and 1=0; * * [PLAN] * QUERY PLAN * ------------------------------------------------------ * CTE Scan on tmp_reuslt * CTE tmp_reuslt * -> StartWith Operator * Start With pseudo atts: RUITR, array_key_9 * -> Recursive Union * -> Seq Scan on test_hcb_ptb * Filter: (id = 141) * -> Result * One-Time Filter: false * (9 rows) * * */ if (IsA(node, RecursiveUnion) && IsA(rplan, BaseResult) && context->botNode == NULL) { context->done = true; StackVarnoPush(varnoStack, INNER_VAR); /* print-out debug information */ ereport(DEBUG1, (errmodule(MOD_OPT_PLANNER), errmsg("SWCB query %s does not generate WTScan", t_thrd.postgres_cxt.debug_query_string))); break; } /* * Before drill down into outer/inner plan tree, we set varno as OUTER_VAR/INNER for * current level of plan node, as suppose the next outer/inner plan will build the final * plan access path */ if (!context->done) { StackVarnoPush(varnoStack, OUTER_VAR); } GetWorkTableScanPlanPath(root, lplan, context); if (!context->done) { StackVarnoPop(varnoStack); } /* process right tree */ if (!context->done) { /* set varno for current level of plan node, we need skip once lplan is done */ StackVarnoPush(varnoStack, INNER_VAR); } GetWorkTableScanPlanPath(root, rplan, context); if (!context->done) { StackVarnoPop(varnoStack); } } } /* dequeue() */ if (!context->done) { StackNodePop(planStack); } return; } /* * -------------------------------------------------------------------------------------- * @brief: * We do some special post-planing work for each StartWithOp node, normally, invokced from * top planning level(root->query_level == 1)'s subplans, detail as follow: * 1. Block some unspported case identified at planning stage * 2. Add internal targetlist(ruitr, array_key, array_col) for Startwith * 3. DFX to support output internal columns from a start with converted CTE * * * @Note: There are two cases of of pseudo entries push-down for internal targetlist * case1: StartWithOp => WorkTableScan, level information is store in WTScan and can be * return to RecursiveUnion * case2: TOP-1/TOP => CteScan, internal information need pop up to query's targetlist level, * so that connect_by_root(), sys_connect_by_path() can get proper information * * Result --------------- * | | * ... | * | case2: (ruitr/array_key/array_col) * CteScan | * | | * StartWithOp --------------- * | | * RecursiveUnion | * | | * HashJoin case1: (level/isleaf/iscycle,ruitr/array_key/array_col) * | | * ... | * | | * WorkTableScan --------------- * * -------------------------------------------------------------------------------------- */ void ProcessStartWithOpMixWork(PlannerInfo *root, Plan *topplan, PlannerInfo *subroot, StartWithOp *swplan) { Assert (IsA(swplan, StartWithOp)); /* * 1. Build fullEntry from current StartWithOp node to "start with" * WorkTable */ PushDownFullPseudoTargetlist(root, (Plan *)swplan->ruplan, NULL, swplan->fullEntryList); /* * 2. Add internalEntryList to CteScan node to support SWCB functions, by defualt * we add full internal entries, only do this when there is SWCB functions exits, * where check the StartWithOp's colEntryList */ if (swplan->colEntryList != NIL) { CteScan *cteplan = swplan->cteplan; PushDownFullPseudoTargetlist(root, (Plan *)cteplan, (Plan *)cteplan, swplan->internalEntryList); } /* * 3. Removing the unnecessary "Internal Target Entry" created by PRC push-down, * ruitr/array_key/array_col, normally this kind of entry only visible on * CteScan node */ if (!u_sess->attr.attr_sql.enable_startwith_debug) { List *newList = NIL; ListCell *lc = NULL; foreach (lc, topplan->targetlist) { TargetEntry *entry = (TargetEntry *)lfirst(lc); if (IsPseudoInternalTargetEntry(entry)) { continue; } newList = lappend(newList, entry); } topplan->targetlist = newList; } return; }