文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

PostgreSQL中Insert语句如何使用

2024-04-02 19:55

关注

PostgreSQL中Insert语句如何使用,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

一、源码解读

standard_planner函数,生成PlannedStmt,其中最重要的信息是可用于后续执行SQL语句的planTree.

 PlannedStmt *
 standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 {
     PlannedStmt *result;//返回结果
     PlannerGlobal *glob;//全局的Plan信息-Global information for planning/optimization
     double      tuple_fraction;//
     PlannerInfo *root;//每个Query的Plan信息-Per-query information for planning/optimization
     RelOptInfo *final_rel;//Plan中的每个Relation信息-Per-relation information for planning/optimization
     Path       *best_path;//最优路径
     Plan       *top_plan;//最上层的Plan
     ListCell   *lp,//临时变量
                *lr;
 
     
     glob = makeNode(PlannerGlobal);//构建PlannerGlobal
   //初始化参数
     glob->boundParams = boundParams;
     glob->subplans = NIL;
     glob->subroots = NIL;
     glob->rewindPlanIDs = NULL;
     glob->finalrtable = NIL;
     glob->finalrowmarks = NIL;
     glob->resultRelations = NIL;
     glob->nonleafResultRelations = NIL;
     glob->rootResultRelations = NIL;
     glob->relationOids = NIL;
     glob->invalItems = NIL;
     glob->paramExecTypes = NIL;
     glob->lastPHId = 0;
     glob->lastRowMarkId = 0;
     glob->lastPlanNodeId = 0;
     glob->transientPlan = false;
     glob->dependsOnRole = false;
 
     
     if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
         IsUnderPostmaster &&
         parse->commandType == CMD_SELECT &&
         !parse->hasModifyingCTE &&
         max_parallel_workers_per_gather > 0 &&
         !IsParallelWorker() &&
         !IsolationIsSerializable())//并行模式的判断
     {
         
         glob->maxParallelHazard = max_parallel_hazard(parse);
         glob->parallelModeOK = (glob->maxParallelHazard != PROPARALLEL_UNSAFE);
     }
     else
     {
         
         glob->maxParallelHazard = PROPARALLEL_UNSAFE;
         glob->parallelModeOK = false;
     }
 
     
     glob->parallelModeNeeded = glob->parallelModeOK &&
         (force_parallel_mode != FORCE_PARALLEL_OFF);
 
     
     if (cursorOptions & CURSOR_OPT_FAST_PLAN)
     {
         
         tuple_fraction = cursor_tuple_fraction;//使用GUC 参数
 
         
         if (tuple_fraction >= 1.0)
             tuple_fraction = 0.0;
         else if (tuple_fraction <= 0.0)
             tuple_fraction = 1e-10;
     }
     else
     {
         
         tuple_fraction = 0.0;
     }
 
     
     root = subquery_planner(glob, parse, NULL,
                             false, tuple_fraction);//获取PlannerInfo根节点
 
     
     final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);//获取顶层的RelOptInfo
     best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);//选择最佳路径
 
     top_plan = create_plan(root, best_path);//生成执行计划
 
     
     if (cursorOptions & CURSOR_OPT_SCROLL)
     {
         if (!ExecSupportsBackwardScan(top_plan))
             top_plan = materialize_finished_plan(top_plan);
     }
 
     
     if (force_parallel_mode != FORCE_PARALLEL_OFF && top_plan->parallel_safe)
     {
         Gather     *gather = makeNode(Gather);
 
         
         gather->plan.initPlan = top_plan->initPlan;
         top_plan->initPlan = NIL;
 
         gather->plan.targetlist = top_plan->targetlist;
         gather->plan.qual = NIL;
         gather->plan.lefttree = top_plan;
         gather->plan.righttree = NULL;
         gather->num_workers = 1;
         gather->single_copy = true;
         gather->invisible = (force_parallel_mode == FORCE_PARALLEL_REGRESS);
 
         
         gather->rescan_param = -1;
 
         
         gather->plan.startup_cost = top_plan->startup_cost +
             parallel_setup_cost;
         gather->plan.total_cost = top_plan->total_cost +
             parallel_setup_cost + parallel_tuple_cost * top_plan->plan_rows;
         gather->plan.plan_rows = top_plan->plan_rows;
         gather->plan.plan_width = top_plan->plan_width;
         gather->plan.parallel_aware = false;
         gather->plan.parallel_safe = false;
 
         
         root->glob->parallelModeNeeded = true;
 
         top_plan = &gather->plan;
     }
 
     
     if (glob->paramExecTypes != NIL)
     {
         Assert(list_length(glob->subplans) == list_length(glob->subroots));
         forboth(lp, glob->subplans, lr, glob->subroots)
         {
             Plan       *subplan = (Plan *) lfirst(lp);
             PlannerInfo *subroot = lfirst_node(PlannerInfo, lr);
 
             SS_finalize_plan(subroot, subplan);
         }
         SS_finalize_plan(root, top_plan);
     }
 
     
     Assert(glob->finalrtable == NIL);
     Assert(glob->finalrowmarks == NIL);
     Assert(glob->resultRelations == NIL);
     Assert(glob->nonleafResultRelations == NIL);
     Assert(glob->rootResultRelations == NIL);
     top_plan = set_plan_references(root, top_plan);
     
     Assert(list_length(glob->subplans) == list_length(glob->subroots));
     forboth(lp, glob->subplans, lr, glob->subroots)
     {
         Plan       *subplan = (Plan *) lfirst(lp);
         PlannerInfo *subroot = lfirst_node(PlannerInfo, lr);
 
         lfirst(lp) = set_plan_references(subroot, subplan);
     }
 
     
     result = makeNode(PlannedStmt);
 
     result->commandType = parse->commandType;//命令类型
     result->queryId = parse->queryId;
     result->hasReturning = (parse->returningList != NIL);
     result->hasModifyingCTE = parse->hasModifyingCTE;
     result->canSetTag = parse->canSetTag;
     result->transientPlan = glob->transientPlan;
     result->dependsOnRole = glob->dependsOnRole;
     result->parallelModeNeeded = glob->parallelModeNeeded;
     result->planTree = top_plan;//执行计划(这是后续执行SQL使用到的最重要的地方)
     result->rtable = glob->finalrtable;
     result->resultRelations = glob->resultRelations;
     result->nonleafResultRelations = glob->nonleafResultRelations;
     result->rootResultRelations = glob->rootResultRelations;
     result->subplans = glob->subplans;
     result->rewindPlanIDs = glob->rewindPlanIDs;
     result->rowMarks = glob->finalrowmarks;
     result->relationOids = glob->relationOids;
     result->invalItems = glob->invalItems;
     result->paramExecTypes = glob->paramExecTypes;
     
     result->utilityStmt = parse->utilityStmt;
     result->stmt_location = parse->stmt_location;
     result->stmt_len = parse->stmt_len;
 
     result->jitFlags = PGJIT_NONE;
     if (jit_enabled && jit_above_cost >= 0 &&
         top_plan->total_cost > jit_above_cost)
     {
         result->jitFlags |= PGJIT_PERFORM;
 
         
         if (jit_optimize_above_cost >= 0 &&
             top_plan->total_cost > jit_optimize_above_cost)
             result->jitFlags |= PGJIT_OPT3;
         if (jit_inline_above_cost >= 0 &&
             top_plan->total_cost > jit_inline_above_cost)
             result->jitFlags |= PGJIT_INLINE;
 
         
         if (jit_expressions)
             result->jitFlags |= PGJIT_EXPR;
         if (jit_tuple_deforming)
             result->jitFlags |= PGJIT_DEFORM;
     }
 
     return result;
 }

二、基础信息

standard_planner函数使用的数据结构、宏定义以及依赖的函数等。
数据结构/宏定义
1、PlannerGlobal


 typedef struct PlannerGlobal
 {
   NodeTag     type;
   ParamListInfo boundParams;  
   List       *subplans;       
   List       *subroots;       
   Bitmapset  *rewindPlanIDs;  
   List       *finalrtable;    
   List       *finalrowmarks;  
   List       *resultRelations;    
   List       *nonleafResultRelations; 
   List       *rootResultRelations;    
   List       *relationOids;   
   List       *invalItems;     
   List       *paramExecTypes; 
   Index       lastPHId;       
   Index       lastRowMarkId;  
   int         lastPlanNodeId; 
   bool        transientPlan;  
   bool        dependsOnRole;  
   bool        parallelModeOK; 
   bool        parallelModeNeeded; 
   char        maxParallelHazard;  
 } PlannerGlobal;

2、PlannerInfo

 
 struct AppendRelInfo;
 
 typedef struct PlannerInfo
 {
     NodeTag     type;
 
     Query      *parse;          
 
     PlannerGlobal *glob;        
 
     Index       query_level;    
 
     struct PlannerInfo *parent_root;    
 
     
     List       *plan_params;    
     Bitmapset  *outer_params;
 
     
     struct RelOptInfo **simple_rel_array;   
     int         simple_rel_array_size;  
 
     
     RangeTblEntry **simple_rte_array;   
 
     
     struct AppendRelInfo **append_rel_array;
 
     
     Relids      all_baserels;
 
     
     Relids      nullable_baserels;
 
     
     List       *join_rel_list;  
     struct HTAB *join_rel_hash; 
 
     
     List      **join_rel_level; 
     int         join_cur_level; 
 
     List       *init_plans;     
 
     List       *cte_plan_ids;   
 
     List       *multiexpr_params;   
 
     List       *eq_classes;     
 
     List       *canon_pathkeys; 
 
     List       *left_join_clauses;  
 
     List       *right_join_clauses; 
 
     List       *full_join_clauses;  
 
     List       *join_info_list; 
 
     List       *append_rel_list;    
 
     List       *rowMarks;       
 
     List       *placeholder_list;   
 
     List       *fkey_list;      
 
     List       *query_pathkeys; 
 
     List       *group_pathkeys; 
     List       *window_pathkeys;    
     List       *distinct_pathkeys;  
     List       *sort_pathkeys;  
 
     List       *part_schemes;   
 
     List       *initial_rels;   
 
     
     List       *upper_rels[UPPERREL_FINAL + 1]; 
 
     
     struct PathTarget *upper_targets[UPPERREL_FINAL + 1];//参见UpperRelationKind
 
     
     List       *processed_tlist;
 
     
     AttrNumber *grouping_map;   
     List       *minmax_aggs;    
 
     MemoryContext planner_cxt;  
 
     double      total_table_pages;  
 
     double      tuple_fraction; 
     double      limit_tuples;   
 
     Index       qual_security_level;    
     
 
     InheritanceKind inhTargetKind;  
     bool        hasJoinRTEs;    
     bool        hasLateralRTEs; 
     bool        hasDeletedRTEs; 
     bool        hasHavingQual;  
     bool        hasPseudoConstantQuals; 
     bool        hasRecursion;   
 
     
     int         wt_param_id;    
     struct Path *non_recursive_path;    
 
     
     Relids      curOuterRels;   
     List       *curOuterParams; 
 
     
     void       *join_search_private;
 
     
     bool        partColsUpdated;
 } PlannerInfo;
 
 
 typedef enum UpperRelationKind
 {
     UPPERREL_SETOP,             
     UPPERREL_PARTIAL_GROUP_AGG, 
     UPPERREL_GROUP_AGG,         
     UPPERREL_WINDOW,            
     UPPERREL_DISTINCT,          
     UPPERREL_ORDERED,           
     UPPERREL_FINAL              
     
 } UpperRelationKind;

3、RangeTblEntry

 
 typedef enum RTEKind
 {
     RTE_RELATION,               
     RTE_SUBQUERY,               
     RTE_JOIN,                   
     RTE_FUNCTION,               
     RTE_TABLEFUNC,              
     RTE_VALUES,                 
     RTE_CTE,                    
     RTE_NAMEDTUPLESTORE         
 } RTEKind;
 
 typedef struct RangeTblEntry
 {
     NodeTag     type;
 
     RTEKind     rtekind;        
 
     
 
     
     Oid         relid;          
     char        relkind;        
     struct TableSampleClause *tablesample;  
 
     
     Query      *subquery;       
     bool        security_barrier;   
 
     
     JoinType    jointype;       
     List       *joinaliasvars;  
 
     
     List       *functions;      
     bool        funcordinality; 
 
     
     TableFunc  *tablefunc;
 
     
     List       *values_lists;   
 
     
     char       *ctename;        
     Index       ctelevelsup;    
     bool        self_reference; 
 
     
     List       *coltypes;       
     List       *coltypmods;     
     List       *colcollations;  
 
     
     char       *enrname;        
     double      enrtuples;      
 
     
     Alias      *alias;          
     Alias      *eref;           
     bool        lateral;        
     bool        inh;            
     bool        inFromCl;       
     AclMode     requiredPerms;  
     Oid         checkAsUser;    
     Bitmapset  *selectedCols;   
     Bitmapset  *insertedCols;   
     Bitmapset  *updatedCols;    
     List       *securityQuals;  
 } RangeTblEntry;

4、TargetEntry

 
 typedef struct TargetEntry
 {
     Expr        xpr;
     Expr       *expr;           
     AttrNumber  resno;          
     char       *resname;        
     Index       ressortgroupref;    
     Oid         resorigtbl;     
     AttrNumber  resorigcol;     
     bool        resjunk;        
 } TargetEntry;

5、RelOptInfo

 
 typedef enum RelOptKind
 {
     RELOPT_BASEREL,
     RELOPT_JOINREL,
     RELOPT_OTHER_MEMBER_REL,
     RELOPT_OTHER_JOINREL,
     RELOPT_UPPER_REL,
     RELOPT_OTHER_UPPER_REL,
     RELOPT_DEADREL
 } RelOptKind;
 
 
 #define IS_SIMPLE_REL(rel) \
     ((rel)->reloptkind == RELOPT_BASEREL || \
      (rel)->reloptkind == RELOPT_OTHER_MEMBER_REL)
 
 
 #define IS_JOIN_REL(rel)    \
     ((rel)->reloptkind == RELOPT_JOINREL || \
      (rel)->reloptkind == RELOPT_OTHER_JOINREL)
 
 
 #define IS_UPPER_REL(rel)   \
     ((rel)->reloptkind == RELOPT_UPPER_REL || \
      (rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
 
 
 #define IS_OTHER_REL(rel) \
     ((rel)->reloptkind == RELOPT_OTHER_MEMBER_REL || \
      (rel)->reloptkind == RELOPT_OTHER_JOINREL || \
      (rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
 
 typedef struct RelOptInfo
 {
     NodeTag     type;
 
     RelOptKind  reloptkind;
 
     
     Relids      relids;         
 
     
     double      rows;           
 
     
     bool        consider_startup;   
     bool        consider_param_startup; 
     bool        consider_parallel;  
 
     
     struct PathTarget *reltarget;   
 
     
     List       *pathlist;       
     List       *ppilist;        
     List       *partial_pathlist;   
     struct Path *cheapest_startup_path;
     struct Path *cheapest_total_path;
     struct Path *cheapest_unique_path;
     List       *cheapest_parameterized_paths;
 
     
     
     Relids      direct_lateral_relids;  
     Relids      lateral_relids; 
 
     
     Index       relid;
     Oid         reltablespace;  
     RTEKind     rtekind;        
     AttrNumber  min_attr;       
     AttrNumber  max_attr;       
     Relids     *attr_needed;    
     int32      *attr_widths;    
     List       *lateral_vars;   
     Relids      lateral_referencers;    
     List       *indexlist;      
     List       *statlist;       
     BlockNumber pages;          
     double      tuples;
     double      allvisfrac;
     PlannerInfo *subroot;       
     List       *subplan_params; 
     int         rel_parallel_workers;   
 
     
     Oid         serverid;       
     Oid         userid;         
     bool        useridiscurrent;    
     
     struct FdwRoutine *fdwroutine;
     void       *fdw_private;
 
     
     List       *unique_for_rels;    
     List       *non_unique_for_rels;    
 
     
     List       *baserestrictinfo;   
     QualCost    baserestrictcost;   
     Index       baserestrict_min_security;  
     List       *joininfo;       
     bool        has_eclass_joins;   
 
     
     Relids      top_parent_relids;  
 
     
     PartitionScheme part_scheme;    
     int         nparts;         
     struct PartitionBoundInfoData *boundinfo;   
     List       *partition_qual; 
     struct RelOptInfo **part_rels;  
     List      **partexprs;      
     List      **nullable_partexprs; 
     List       *partitioned_child_rels; 
 } RelOptInfo;

6、Path

 
 typedef struct Path
 {
     NodeTag     type;
 
     NodeTag     pathtype;       
 
     RelOptInfo *parent;         
     PathTarget *pathtarget;     
 
     ParamPathInfo *param_info;  
 
     bool        parallel_aware; 
     bool        parallel_safe;  
     int         parallel_workers;   
 
     
     double      rows;           
     Cost        startup_cost;   
     Cost        total_cost;     
 
     List       *pathkeys;       
     
 } Path;
 

 typedef struct PathKey
 {
     NodeTag     type;
 
     EquivalenceClass *pk_eclass;    
     Oid         pk_opfamily;    
     int         pk_strategy;    
     bool        pk_nulls_first; 
 } PathKey;
 
 
 
 
 typedef struct PathTarget
 {
     NodeTag     type;
     List       *exprs;          
     Index      *sortgrouprefs;  
     QualCost    cost;           
     int         width;          
 } PathTarget;
 
 
 #define get_pathtarget_sortgroupref(target, colno) \
     ((target)->sortgrouprefs ? (target)->sortgrouprefs[colno] : (Index) 0)

7、ModifyTable

 
 typedef struct ModifyTable
 {
     Plan        plan;
     CmdType     operation;      
     bool        canSetTag;      
     Index       nominalRelation;    
     
     List       *partitioned_rels;
     bool        partColsUpdated;    
     List       *resultRelations;    
     int         resultRelIndex; 
     int         rootResultRelIndex; 
     List       *plans;          
     List       *withCheckOptionLists;   
     List       *returningLists; 
     List       *fdwPrivLists;   
     Bitmapset  *fdwDirectModifyPlans;   
     List       *rowMarks;       
     int         epqParam;       
     OnConflictAction onConflictAction;  
     List       *arbiterIndexes; 
     List       *onConflictSet;  
     Node       *onConflictWhere;    
     Index       exclRelRTI;     
     List       *exclRelTlist;   
 } ModifyTable;

依赖的函数
1、grouping_planner


 static void
 grouping_planner(PlannerInfo *root, bool inheritance_update,
                  double tuple_fraction)
 {
     Query      *parse = root->parse;
     List       *tlist;
     int64       offset_est = 0;
     int64       count_est = 0;
     double      limit_tuples = -1.0;
     bool        have_postponed_srfs = false;
     PathTarget *final_target;
     List       *final_targets;
     List       *final_targets_contain_srfs;
     bool        final_target_parallel_safe;
     RelOptInfo *current_rel;
     RelOptInfo *final_rel;
     ListCell   *lc;
 
     
     if (parse->limitCount || parse->limitOffset)
     {
         tuple_fraction = preprocess_limit(root, tuple_fraction,
                                           &offset_est, &count_est);
 
         
         if (count_est > 0 && offset_est >= 0)
             limit_tuples = (double) count_est + (double) offset_est;
     }
 
     
     root->tuple_fraction = tuple_fraction;
 
     if (parse->setOperations)
     {
         
         if (parse->sortClause)
             root->tuple_fraction = 0.0;
 
         
         current_rel = plan_set_operations(root);
 
         
         Assert(parse->commandType == CMD_SELECT);
 
         tlist = root->processed_tlist;  
 
         
         tlist = postprocess_setop_tlist(copyObject(tlist), parse->targetList);
 
         
         root->processed_tlist = tlist;
 
         
         final_target = current_rel->cheapest_total_path->pathtarget;
 
         
         final_target_parallel_safe =
             is_parallel_safe(root, (Node *) final_target->exprs);
 
         
         Assert(!parse->hasTargetSRFs);
         final_targets = final_targets_contain_srfs = NIL;
 
         
         if (parse->rowMarks)
             ereport(ERROR,
                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
             
                      errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
                             LCS_asString(linitial_node(RowMarkClause,
                                                        parse->rowMarks)->strength))));
 
         
         Assert(parse->distinctClause == NIL);
         root->sort_pathkeys = make_pathkeys_for_sortclauses(root,
                                                             parse->sortClause,
                                                             tlist);
     }
     else
     {
         
         PathTarget *sort_input_target;
         List       *sort_input_targets;
         List       *sort_input_targets_contain_srfs;
         bool        sort_input_target_parallel_safe;
         PathTarget *grouping_target;
         List       *grouping_targets;
         List       *grouping_targets_contain_srfs;
         bool        grouping_target_parallel_safe;
         PathTarget *scanjoin_target;
         List       *scanjoin_targets;
         List       *scanjoin_targets_contain_srfs;
         bool        scanjoin_target_parallel_safe;
         bool        scanjoin_target_same_exprs;
         bool        have_grouping;
         AggClauseCosts agg_costs;
         WindowFuncLists *wflists = NULL;
         List       *activeWindows = NIL;
         grouping_sets_data *gset_data = NULL;
         standard_qp_extra qp_extra;
 
         
         Assert(!root->hasRecursion);
 
         
         if (parse->groupingSets)
         {
             gset_data = preprocess_grouping_sets(root);
         }
         else
         {
             
             if (parse->groupClause)
                 parse->groupClause = preprocess_groupclause(root, NIL);
         }
 
         
         tlist = preprocess_targetlist(root);
 
         
         root->processed_tlist = tlist;
 
         
         MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
         if (parse->hasAggs)
         {
             get_agg_clause_costs(root, (Node *) tlist, AGGSPLIT_SIMPLE,
                                  &agg_costs);
             get_agg_clause_costs(root, parse->havingQual, AGGSPLIT_SIMPLE,
                                  &agg_costs);
         }
 
         
         if (parse->hasWindowFuncs)
         {
             wflists = find_window_functions((Node *) tlist,
                                             list_length(parse->windowClause));
             if (wflists->numWindowFuncs > 0)
                 activeWindows = select_active_windows(root, wflists);
             else
                 parse->hasWindowFuncs = false;
         }
 
         
         if (parse->hasAggs)
             preprocess_minmax_aggregates(root, tlist);
 
         
         if (parse->groupClause ||
             parse->groupingSets ||
             parse->distinctClause ||
             parse->hasAggs ||
             parse->hasWindowFuncs ||
             parse->hasTargetSRFs ||
             root->hasHavingQual)
             root->limit_tuples = -1.0;
         else
             root->limit_tuples = limit_tuples;
 
         
         qp_extra.tlist = tlist;
         qp_extra.activeWindows = activeWindows;
         qp_extra.groupClause = (gset_data
                                 ? (gset_data->rollups ? linitial_node(RollupData, gset_data->rollups)->groupClause : NIL)
                                 : parse->groupClause);
 
         
         current_rel = query_planner(root, tlist,
                                     standard_qp_callback, &qp_extra);
 
         
         final_target = create_pathtarget(root, tlist);
         final_target_parallel_safe =
             is_parallel_safe(root, (Node *) final_target->exprs);
 
         
         if (parse->sortClause)
         {
             sort_input_target = make_sort_input_target(root,
                                                        final_target,
                                                        &have_postponed_srfs);
             sort_input_target_parallel_safe =
                 is_parallel_safe(root, (Node *) sort_input_target->exprs);
         }
         else
         {
             sort_input_target = final_target;
             sort_input_target_parallel_safe = final_target_parallel_safe;
         }
 
         
         if (activeWindows)
         {
             grouping_target = make_window_input_target(root,
                                                        final_target,
                                                        activeWindows);
             grouping_target_parallel_safe =
                 is_parallel_safe(root, (Node *) grouping_target->exprs);
         }
         else
         {
             grouping_target = sort_input_target;
             grouping_target_parallel_safe = sort_input_target_parallel_safe;
         }
 
         
         have_grouping = (parse->groupClause || parse->groupingSets ||
                          parse->hasAggs || root->hasHavingQual);
         if (have_grouping)
         {
             scanjoin_target = make_group_input_target(root, final_target);
             scanjoin_target_parallel_safe =
                 is_parallel_safe(root, (Node *) grouping_target->exprs);
         }
         else
         {
             scanjoin_target = grouping_target;
             scanjoin_target_parallel_safe = grouping_target_parallel_safe;
         }
 
         
         if (parse->hasTargetSRFs)
         {
             
             split_pathtarget_at_srfs(root, final_target, sort_input_target,
                                      &final_targets,
                                      &final_targets_contain_srfs);
             final_target = linitial_node(PathTarget, final_targets);
             Assert(!linitial_int(final_targets_contain_srfs));
             
             split_pathtarget_at_srfs(root, sort_input_target, grouping_target,
                                      &sort_input_targets,
                                      &sort_input_targets_contain_srfs);
             sort_input_target = linitial_node(PathTarget, sort_input_targets);
             Assert(!linitial_int(sort_input_targets_contain_srfs));
             
             split_pathtarget_at_srfs(root, grouping_target, scanjoin_target,
                                      &grouping_targets,
                                      &grouping_targets_contain_srfs);
             grouping_target = linitial_node(PathTarget, grouping_targets);
             Assert(!linitial_int(grouping_targets_contain_srfs));
             
             split_pathtarget_at_srfs(root, scanjoin_target, NULL,
                                      &scanjoin_targets,
                                      &scanjoin_targets_contain_srfs);
             scanjoin_target = linitial_node(PathTarget, scanjoin_targets);
             Assert(!linitial_int(scanjoin_targets_contain_srfs));
         }
         else
         {
             
             final_targets = final_targets_contain_srfs = NIL;
             sort_input_targets = sort_input_targets_contain_srfs = NIL;
             grouping_targets = grouping_targets_contain_srfs = NIL;
             scanjoin_targets = list_make1(scanjoin_target);
             scanjoin_targets_contain_srfs = NIL;
         }
 
         
         scanjoin_target_same_exprs = list_length(scanjoin_targets) == 1
             && equal(scanjoin_target->exprs, current_rel->reltarget->exprs);
         apply_scanjoin_target_to_paths(root, current_rel, scanjoin_targets,
                                        scanjoin_targets_contain_srfs,
                                        scanjoin_target_parallel_safe,
                                        scanjoin_target_same_exprs);
 
         
         root->upper_targets[UPPERREL_FINAL] = final_target;
         root->upper_targets[UPPERREL_WINDOW] = sort_input_target;
         root->upper_targets[UPPERREL_GROUP_AGG] = grouping_target;
 
         
         if (have_grouping)
         {
             current_rel = create_grouping_paths(root,
                                                 current_rel,
                                                 grouping_target,
                                                 grouping_target_parallel_safe,
                                                 &agg_costs,
                                                 gset_data);
             
             if (parse->hasTargetSRFs)
                 adjust_paths_for_srfs(root, current_rel,
                                       grouping_targets,
                                       grouping_targets_contain_srfs);
         }
 
         
         if (activeWindows)
         {
             current_rel = create_window_paths(root,
                                               current_rel,
                                               grouping_target,
                                               sort_input_target,
                                               sort_input_target_parallel_safe,
                                               tlist,
                                               wflists,
                                               activeWindows);
             
             if (parse->hasTargetSRFs)
                 adjust_paths_for_srfs(root, current_rel,
                                       sort_input_targets,
                                       sort_input_targets_contain_srfs);
         }
 
         
         if (parse->distinctClause)
         {
             current_rel = create_distinct_paths(root,
                                                 current_rel);
         }
     }                           
 
     
     if (parse->sortClause)
     {
         current_rel = create_ordered_paths(root,
                                            current_rel,
                                            final_target,
                                            final_target_parallel_safe,
                                            have_postponed_srfs ? -1.0 :
                                            limit_tuples);
         
         if (parse->hasTargetSRFs)
             adjust_paths_for_srfs(root, current_rel,
                                   final_targets,
                                   final_targets_contain_srfs);
     }
 
     
     final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
 
     
     if (current_rel->consider_parallel &&
         is_parallel_safe(root, parse->limitOffset) &&
         is_parallel_safe(root, parse->limitCount))
         final_rel->consider_parallel = true;
 
     
     final_rel->serverid = current_rel->serverid;
     final_rel->userid = current_rel->userid;
     final_rel->useridiscurrent = current_rel->useridiscurrent;
     final_rel->fdwroutine = current_rel->fdwroutine;
 
     
     foreach(lc, current_rel->pathlist)
     {
         Path       *path = (Path *) lfirst(lc);
 
         
         if (parse->rowMarks)
         {
             path = (Path *) create_lockrows_path(root, final_rel, path,
                                                  root->rowMarks,
                                                  SS_assign_special_param(root));
         }
 
         
         if (limit_needed(parse))
         {
             path = (Path *) create_limit_path(root, final_rel, path,
                                               parse->limitOffset,
                                               parse->limitCount,
                                               offset_est, count_est);
         }
 
         
         if (parse->commandType != CMD_SELECT && !inheritance_update)
         {
             List       *withCheckOptionLists;
             List       *returningLists;
             List       *rowMarks;
 
             
             if (parse->withCheckOptions)
                 withCheckOptionLists = list_make1(parse->withCheckOptions);
             else
                 withCheckOptionLists = NIL;
 
             if (parse->returningList)
                 returningLists = list_make1(parse->returningList);
             else
                 returningLists = NIL;
 
             
             if (parse->rowMarks)
                 rowMarks = NIL;
             else
                 rowMarks = root->rowMarks;
 
             path = (Path *)
                 create_modifytable_path(root, final_rel,
                                         parse->commandType,
                                         parse->canSetTag,
                                         parse->resultRelation,
                                         NIL,
                                         false,
                                         list_make1_int(parse->resultRelation),
                                         list_make1(path),
                                         list_make1(root),
                                         withCheckOptionLists,
                                         returningLists,
                                         rowMarks,
                                         parse->onConflict,
                                         SS_assign_special_param(root));
         }
 
         
         add_path(final_rel, path);
     }
 
     
     if (final_rel->consider_parallel && root->query_level > 1 &&
         !limit_needed(parse))
     {
         Assert(!parse->rowMarks && parse->commandType == CMD_SELECT);
         foreach(lc, current_rel->partial_pathlist)
         {
             Path       *partial_path = (Path *) lfirst(lc);
 
             add_partial_path(final_rel, partial_path);
         }
     }
 
     
     if (final_rel->fdwroutine &&
         final_rel->fdwroutine->GetForeignUpperPaths)
         final_rel->fdwroutine->GetForeignUpperPaths(root, UPPERREL_FINAL,
                                                     current_rel, final_rel,
                                                     NULL);
 
     
     if (create_upper_paths_hook)
         (*create_upper_paths_hook) (root, UPPERREL_FINAL,
                                     current_rel, final_rel, NULL);
 
     
 }

 
 RelOptInfo *
 query_planner(PlannerInfo *root, List *tlist,
               query_pathkeys_callback qp_callback, void *qp_extra)
 {
     Query      *parse = root->parse;
     List       *joinlist;
     RelOptInfo *final_rel;
     Index       rti;
     double      total_pages;
 
     
     if (parse->jointree->fromlist == NIL)
     {
         
         final_rel = build_empty_join_rel(root);
 
         
         if (root->glob->parallelModeOK)
             final_rel->consider_parallel =
                 is_parallel_safe(root, parse->jointree->quals);
 
         
         add_path(final_rel, (Path *)
                  create_result_path(root, final_rel,
                                     final_rel->reltarget,
                                     (List *) parse->jointree->quals));
 
         
         set_cheapest(final_rel);
 
         
         root->canon_pathkeys = NIL;
         (*qp_callback) (root, qp_extra);
 
         return final_rel;
  }

   
 ModifyTablePath *
 create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
                         CmdType operation, bool canSetTag,
                         Index nominalRelation, List *partitioned_rels,
                         bool partColsUpdated,
                         List *resultRelations, List *subpaths,
                         List *subroots,
                         List *withCheckOptionLists, List *returningLists,
                         List *rowMarks, OnConflictExpr *onconflict,
                         int epqParam)
 {
     ModifyTablePath *pathnode = makeNode(ModifyTablePath);
     double      total_size;
     ListCell   *lc;
 
     Assert(list_length(resultRelations) == list_length(subpaths));
     Assert(list_length(resultRelations) == list_length(subroots));
     Assert(withCheckOptionLists == NIL ||
            list_length(resultRelations) == list_length(withCheckOptionLists));
     Assert(returningLists == NIL ||
            list_length(resultRelations) == list_length(returningLists));
 
     pathnode->path.pathtype = T_ModifyTable;
     pathnode->path.parent = rel;
     
     pathnode->path.pathtarget = rel->reltarget;
     
     pathnode->path.param_info = NULL;
     pathnode->path.parallel_aware = false;
     pathnode->path.parallel_safe = false;
     pathnode->path.parallel_workers = 0;
     pathnode->path.pathkeys = NIL;
 
     
     pathnode->path.startup_cost = 0;
     pathnode->path.total_cost = 0;
     pathnode->path.rows = 0;
     total_size = 0;
     foreach(lc, subpaths)
     {
         Path       *subpath = (Path *) lfirst(lc);
 
         if (lc == list_head(subpaths))  
             pathnode->path.startup_cost = subpath->startup_cost;
         pathnode->path.total_cost += subpath->total_cost;
         pathnode->path.rows += subpath->rows;
         total_size += subpath->pathtarget->width * subpath->rows;
     }
 
     
     if (pathnode->path.rows > 0)
         total_size /= pathnode->path.rows;
     pathnode->path.pathtarget->width = rint(total_size);
 
     pathnode->operation = operation;
     pathnode->canSetTag = canSetTag;
     pathnode->nominalRelation = nominalRelation;
     pathnode->partitioned_rels = list_copy(partitioned_rels);
     pathnode->partColsUpdated = partColsUpdated;
     pathnode->resultRelations = resultRelations;
     pathnode->subpaths = subpaths;
     pathnode->subroots = subroots;
     pathnode->withCheckOptionLists = withCheckOptionLists;
     pathnode->returningLists = returningLists;
     pathnode->rowMarks = rowMarks;
     pathnode->onconflict = onconflict;
     pathnode->epqParam = epqParam;
 
     return pathnode;
 }

2、subquery_planner

 

 PlannerInfo *
 subquery_planner(PlannerGlobal *glob, Query *parse,
                  PlannerInfo *parent_root,
                  bool hasRecursion, double tuple_fraction)
 {
     PlannerInfo *root;//返回值
     List       *newWithCheckOptions;//
     List       *newHaving;//Having子句
     bool        hasOuterJoins;//是否存在Outer Join?
     RelOptInfo *final_rel;//
     ListCell   *l;//临时变量
 
     
     root = makeNode(PlannerInfo);//构造返回值
     root->parse = parse;
     root->glob = glob;
     root->query_level = parent_root ? parent_root->query_level + 1 : 1;
     root->parent_root = parent_root;
     root->plan_params = NIL;
     root->outer_params = NULL;
     root->planner_cxt = CurrentMemoryContext;
     root->init_plans = NIL;
     root->cte_plan_ids = NIL;
     root->multiexpr_params = NIL;
     root->eq_classes = NIL;
     root->append_rel_list = NIL;
     root->rowMarks = NIL;
     memset(root->upper_rels, 0, sizeof(root->upper_rels));
     memset(root->upper_targets, 0, sizeof(root->upper_targets));
     root->processed_tlist = NIL;
     root->grouping_map = NULL;
     root->minmax_aggs = NIL;
     root->qual_security_level = 0;
     root->inhTargetKind = INHKIND_NONE;
     root->hasRecursion = hasRecursion;
     if (hasRecursion)
         root->wt_param_id = SS_assign_special_param(root);
     else
         root->wt_param_id = -1;
     root->non_recursive_path = NULL;
     root->partColsUpdated = false;
 
     
     if (parse->cteList)
         SS_process_ctes(root);//With 语句
 
     
     if (parse->hasSubLinks)
         pull_up_sublinks(root); //转换ANY/EXISTS为JOIN
 
     
     inline_set_returning_functions(root);
 
     
     pull_up_subqueries(root);//
 
     
     if (parse->setOperations)
         flatten_simple_union_all(root);
 
     
     root->hasJoinRTEs = false;
     root->hasLateralRTEs = false;
     hasOuterJoins = false;
     foreach(l, parse->rtable)
     {
         RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
 
         if (rte->rtekind == RTE_JOIN)
         {
             root->hasJoinRTEs = true;
             if (IS_OUTER_JOIN(rte->jointype))
                 hasOuterJoins = true;
         }
         if (rte->lateral)
             root->hasLateralRTEs = true;
     }
 
     
     preprocess_rowmarks(root);
 
     
     expand_inherited_tables(root);
 
     
     root->hasHavingQual = (parse->havingQual != NULL);
 
     
     root->hasPseudoConstantQuals = false;
 
     
     parse->targetList = (List *)
         preprocess_expression(root, (Node *) parse->targetList,
                               EXPRKIND_TARGET);
 
     
     if (parse->hasTargetSRFs)
         parse->hasTargetSRFs = expression_returns_set((Node *) parse->targetList);
 
     newWithCheckOptions = NIL;
     foreach(l, parse->withCheckOptions)
     {
         WithCheckOption *wco = lfirst_node(WithCheckOption, l);
 
         wco->qual = preprocess_expression(root, wco->qual,
                                           EXPRKIND_QUAL);
         if (wco->qual != NULL)
             newWithCheckOptions = lappend(newWithCheckOptions, wco);
     }
     parse->withCheckOptions = newWithCheckOptions;
 
     parse->returningList = (List *)
         preprocess_expression(root, (Node *) parse->returningList,
                               EXPRKIND_TARGET);
 
     preprocess_qual_conditions(root, (Node *) parse->jointree);
 
     parse->havingQual = preprocess_expression(root, parse->havingQual,
                                               EXPRKIND_QUAL);
 
     foreach(l, parse->windowClause)
     {
         WindowClause *wc = lfirst_node(WindowClause, l);
 
         
         wc->startOffset = preprocess_expression(root, wc->startOffset,
                                                 EXPRKIND_LIMIT);
         wc->endOffset = preprocess_expression(root, wc->endOffset,
                                               EXPRKIND_LIMIT);
     }
 
     parse->limitOffset = preprocess_expression(root, parse->limitOffset,
                                                EXPRKIND_LIMIT);
     parse->limitCount = preprocess_expression(root, parse->limitCount,
                                               EXPRKIND_LIMIT);
 
     if (parse->onConflict)
     {
         parse->onConflict->arbiterElems = (List *)
             preprocess_expression(root,
                                   (Node *) parse->onConflict->arbiterElems,
                                   EXPRKIND_ARBITER_ELEM);
         parse->onConflict->arbiterWhere =
             preprocess_expression(root,
                                   parse->onConflict->arbiterWhere,
                                   EXPRKIND_QUAL);
         parse->onConflict->onConflictSet = (List *)
             preprocess_expression(root,
                                   (Node *) parse->onConflict->onConflictSet,
                                   EXPRKIND_TARGET);
         parse->onConflict->onConflictWhere =
             preprocess_expression(root,
                                   parse->onConflict->onConflictWhere,
                                   EXPRKIND_QUAL);
         
     }
 
     root->append_rel_list = (List *)
         preprocess_expression(root, (Node *) root->append_rel_list,
                               EXPRKIND_APPINFO);
 
     
     foreach(l, parse->rtable)
     {
         RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
         int         kind;
         ListCell   *lcsq;
 
         if (rte->rtekind == RTE_RELATION)
         {
             if (rte->tablesample)
                 rte->tablesample = (TableSampleClause *)
                     preprocess_expression(root,
                                           (Node *) rte->tablesample,
                                           EXPRKIND_TABLESAMPLE);
         }
         else if (rte->rtekind == RTE_SUBQUERY)
         {
             
             if (rte->lateral && root->hasJoinRTEs)
                 rte->subquery = (Query *)
                     flatten_join_alias_vars(root, (Node *) rte->subquery);
         }
         else if (rte->rtekind == RTE_FUNCTION)
         {
             
             kind = rte->lateral ? EXPRKIND_RTFUNC_LATERAL : EXPRKIND_RTFUNC;
             rte->functions = (List *)
                 preprocess_expression(root, (Node *) rte->functions, kind);
         }
         else if (rte->rtekind == RTE_TABLEFUNC)
         {
             
             kind = rte->lateral ? EXPRKIND_TABLEFUNC_LATERAL : EXPRKIND_TABLEFUNC;
             rte->tablefunc = (TableFunc *)
                 preprocess_expression(root, (Node *) rte->tablefunc, kind);
         }
         else if (rte->rtekind == RTE_VALUES)
         {
             
             kind = rte->lateral ? EXPRKIND_VALUES_LATERAL : EXPRKIND_VALUES;
             rte->values_lists = (List *)
                 preprocess_expression(root, (Node *) rte->values_lists, kind);
         }
 
         
         foreach(lcsq, rte->securityQuals)
         {
             lfirst(lcsq) = preprocess_expression(root,
                                                  (Node *) lfirst(lcsq),
                                                  EXPRKIND_QUAL);
         }
     }
 
     
     if (root->hasJoinRTEs)
     {
         foreach(l, parse->rtable)
         {
             RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
 
             rte->joinaliasvars = NIL;
         }
     }
 
     
     newHaving = NIL;
     foreach(l, (List *) parse->havingQual)
     {
         Node       *havingclause = (Node *) lfirst(l);
 
         if ((parse->groupClause && parse->groupingSets) ||
             contain_agg_clause(havingclause) ||
             contain_volatile_functions(havingclause) ||
             contain_subplans(havingclause))
         {
             
             newHaving = lappend(newHaving, havingclause);
         }
         else if (parse->groupClause && !parse->groupingSets)
         {
             
             parse->jointree->quals = (Node *)
                 lappend((List *) parse->jointree->quals, havingclause);
         }
         else
         {
             
             parse->jointree->quals = (Node *)
                 lappend((List *) parse->jointree->quals,
                         copyObject(havingclause));
             newHaving = lappend(newHaving, havingclause);
         }
     }
     parse->havingQual = (Node *) newHaving;
 
     
     remove_useless_groupby_columns(root);
 
     
     if (hasOuterJoins)
         reduce_outer_joins(root);
 
     
     if (parse->resultRelation &&
         rt_fetch(parse->resultRelation, parse->rtable)->inh)
         inheritance_planner(root);
     else
         grouping_planner(root, false, tuple_fraction);
 
     
     SS_identify_outer_params(root);
 
     
     final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
     SS_charge_for_initplans(root, final_rel);
 
     
     set_cheapest(final_rel);
 
     return root;
 }

3.create_plan

 
 Plan *
 create_plan(PlannerInfo *root, Path *best_path)
 {
     Plan       *plan;
 
     
     Assert(root->plan_params == NIL);
 
     
     root->curOuterRels = NULL;
     root->curOuterParams = NIL;
 
     
     plan = create_plan_recurse(root, best_path, CP_EXACT_TLIST);
 
     
     if (!IsA(plan, ModifyTable))
         apply_tlist_labeling(plan->targetlist, root->processed_tlist);
 
     
     SS_attach_initplans(root, plan);
 
     
     if (root->curOuterParams != NIL)
         elog(ERROR, "failed to assign all NestLoopParams to plan nodes");
 
     
     root->plan_params = NIL;
 
     return plan;
 }


 static Plan *
 create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
 {
     Plan       *plan;
 
     
     check_stack_depth();
 
     switch (best_path->pathtype)
     {
         case T_SeqScan:
         case T_SampleScan:
         case T_IndexScan:
         case T_IndexOnlyScan:
         case T_BitmapHeapScan:
         case T_TidScan:
         case T_SubqueryScan:
         case T_FunctionScan:
         case T_TableFuncScan:
         case T_ValuesScan:
         case T_CteScan:
         case T_WorkTableScan:
         case T_NamedTuplestoreScan:
         case T_ForeignScan:
         case T_CustomScan:
             plan = create_scan_plan(root, best_path, flags);
             break;
         case T_HashJoin:
         case T_MergeJoin:
         case T_NestLoop:
             plan = create_join_plan(root,
                                     (JoinPath *) best_path);
             break;
         case T_Append:
             plan = create_append_plan(root,
                                       (AppendPath *) best_path);
             break;
         case T_MergeAppend:
             plan = create_merge_append_plan(root,
                                             (MergeAppendPath *) best_path);
             break;
         case T_Result:
             if (IsA(best_path, ProjectionPath))
             {
                 plan = create_projection_plan(root,
                                               (ProjectionPath *) best_path,
                                               flags);
             }
             else if (IsA(best_path, MinMaxAggPath))
             {
                 plan = (Plan *) create_minmaxagg_plan(root,
                                                       (MinMaxAggPath *) best_path);
             }
             else
             {
                 Assert(IsA(best_path, ResultPath));
                 plan = (Plan *) create_result_plan(root,
                                                    (ResultPath *) best_path);
             }
             break;
         case T_ProjectSet:
             plan = (Plan *) create_project_set_plan(root,
                                                     (ProjectSetPath *) best_path);
             break;
         case T_Material:
             plan = (Plan *) create_material_plan(root,
                                                  (MaterialPath *) best_path,
                                                  flags);
             break;
         case T_Unique:
             if (IsA(best_path, UpperUniquePath))
             {
                 plan = (Plan *) create_upper_unique_plan(root,
                                                          (UpperUniquePath *) best_path,
                                                          flags);
             }
             else
             {
                 Assert(IsA(best_path, UniquePath));
                 plan = create_unique_plan(root,
                                           (UniquePath *) best_path,
                                           flags);
             }
             break;
         case T_Gather:
             plan = (Plan *) create_gather_plan(root,
                                                (GatherPath *) best_path);
             break;
         case T_Sort:
             plan = (Plan *) create_sort_plan(root,
                                              (SortPath *) best_path,
                                              flags);
             break;
         case T_Group:
             plan = (Plan *) create_group_plan(root,
                                               (GroupPath *) best_path);
             break;
         case T_Agg:
             if (IsA(best_path, GroupingSetsPath))
                 plan = create_groupingsets_plan(root,
                                                 (GroupingSetsPath *) best_path);
             else
             {
                 Assert(IsA(best_path, AggPath));
                 plan = (Plan *) create_agg_plan(root,
                                                 (AggPath *) best_path);
             }
             break;
         case T_WindowAgg:
             plan = (Plan *) create_windowagg_plan(root,
                                                   (WindowAggPath *) best_path);
             break;
         case T_SetOp:
             plan = (Plan *) create_setop_plan(root,
                                               (SetOpPath *) best_path,
                                               flags);
             break;
         case T_RecursiveUnion:
             plan = (Plan *) create_recursiveunion_plan(root,
                                                        (RecursiveUnionPath *) best_path);
             break;
         case T_LockRows:
             plan = (Plan *) create_lockrows_plan(root,
                                                  (LockRowsPath *) best_path,
                                                  flags);
             break;
         case T_ModifyTable:
             plan = (Plan *) create_modifytable_plan(root,
                                                     (ModifyTablePath *) best_path);
             break;
         case T_Limit:
             plan = (Plan *) create_limit_plan(root,
                                               (LimitPath *) best_path,
                                               flags);
             break;
         case T_GatherMerge:
             plan = (Plan *) create_gather_merge_plan(root,
                                                      (GatherMergePath *) best_path);
             break;
         default:
             elog(ERROR, "unrecognized node type: %d",
                  (int) best_path->pathtype);
             plan = NULL;        
             break;
     }
 
     return plan;
 }

 
 static ModifyTable *
 create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 {
     ModifyTable *plan;
     List       *subplans = NIL;
     ListCell   *subpaths,
                *subroots;
 
     
     forboth(subpaths, best_path->subpaths,
             subroots, best_path->subroots)
     {
         Path       *subpath = (Path *) lfirst(subpaths);
         PlannerInfo *subroot = (PlannerInfo *) lfirst(subroots);
         Plan       *subplan;
 
         
         subplan = create_plan_recurse(subroot, subpath, CP_EXACT_TLIST);
 
         
         apply_tlist_labeling(subplan->targetlist, subroot->processed_tlist);
 
         subplans = lappend(subplans, subplan);
     }
 
     plan = make_modifytable(root,
                             best_path->operation,
                             best_path->canSetTag,
                             best_path->nominalRelation,
                             best_path->partitioned_rels,
                             best_path->partColsUpdated,
                             best_path->resultRelations,
                             subplans,
                             best_path->subroots,
                             best_path->withCheckOptionLists,
                             best_path->returningLists,
                             best_path->rowMarks,
                             best_path->onconflict,
                             best_path->epqParam);
 
     copy_generic_path_info(&plan->plan, &best_path->path);
 
     return plan;
 }

 
 static ModifyTable *
 make_modifytable(PlannerInfo *root,
                  CmdType operation, bool canSetTag,
                  Index nominalRelation, List *partitioned_rels,
                  bool partColsUpdated,
                  List *resultRelations, List *subplans, List *subroots,
                  List *withCheckOptionLists, List *returningLists,
                  List *rowMarks, OnConflictExpr *onconflict, int epqParam)
 {
     ModifyTable *node = makeNode(ModifyTable);
     List       *fdw_private_list;
     Bitmapset  *direct_modify_plans;
     ListCell   *lc;
     ListCell   *lc2;
     int         i;
 
     Assert(list_length(resultRelations) == list_length(subplans));
     Assert(list_length(resultRelations) == list_length(subroots));
     Assert(withCheckOptionLists == NIL ||
            list_length(resultRelations) == list_length(withCheckOptionLists));
     Assert(returningLists == NIL ||
            list_length(resultRelations) == list_length(returningLists));
 
     node->plan.lefttree = NULL;
     node->plan.righttree = NULL;
     node->plan.qual = NIL;
     
     node->plan.targetlist = NIL;
 
     node->operation = operation;
     node->canSetTag = canSetTag;
     node->nominalRelation = nominalRelation;
     node->partitioned_rels = flatten_partitioned_rels(partitioned_rels);
     node->partColsUpdated = partColsUpdated;
     node->resultRelations = resultRelations;
     node->resultRelIndex = -1;  
     node->rootResultRelIndex = -1;  
     node->plans = subplans;
     if (!onconflict)
     {
         node->onConflictAction = ONCONFLICT_NONE;
         node->onConflictSet = NIL;
         node->onConflictWhere = NULL;
         node->arbiterIndexes = NIL;
         node->exclRelRTI = 0;
         node->exclRelTlist = NIL;
     }
     else
     {
         node->onConflictAction = onconflict->action;
         node->onConflictSet = onconflict->onConflictSet;
         node->onConflictWhere = onconflict->onConflictWhere;
 
         
         node->arbiterIndexes = infer_arbiter_indexes(root);
 
         node->exclRelRTI = onconflict->exclRelIndex;
         node->exclRelTlist = onconflict->exclRelTlist;
     }
     node->withCheckOptionLists = withCheckOptionLists;
     node->returningLists = returningLists;
     node->rowMarks = rowMarks;
     node->epqParam = epqParam;
 
     
     fdw_private_list = NIL;
     direct_modify_plans = NULL;
     i = 0;
     forboth(lc, resultRelations, lc2, subroots)
     {
         Index       rti = lfirst_int(lc);
         PlannerInfo *subroot = lfirst_node(PlannerInfo, lc2);
         FdwRoutine *fdwroutine;
         List       *fdw_private;
         bool        direct_modify;
 
         
         if (rti < subroot->simple_rel_array_size &&
             subroot->simple_rel_array[rti] != NULL)
         {
             RelOptInfo *resultRel = subroot->simple_rel_array[rti];
 
             fdwroutine = resultRel->fdwroutine;
         }
         else
         {
             RangeTblEntry *rte = planner_rt_fetch(rti, subroot);
 
             Assert(rte->rtekind == RTE_RELATION);
             if (rte->relkind == RELKIND_FOREIGN_TABLE)
                 fdwroutine = GetFdwRoutineByRelId(rte->relid);
             else
                 fdwroutine = NULL;
         }
 
         
         direct_modify = false;
         if (fdwroutine != NULL &&
             fdwroutine->PlanDirectModify != NULL &&
             fdwroutine->BeginDirectModify != NULL &&
             fdwroutine->IterateDirectModify != NULL &&
             fdwroutine->EndDirectModify != NULL &&
             withCheckOptionLists == NIL &&
             !has_row_triggers(subroot, rti, operation))
             direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
         if (direct_modify)
             direct_modify_plans = bms_add_member(direct_modify_plans, i);
 
         if (!direct_modify &&
             fdwroutine != NULL &&
             fdwroutine->PlanForeignModify != NULL)
             fdw_private = fdwroutine->PlanForeignModify(subroot, node, rti, i);
         else
             fdw_private = NIL;
         fdw_private_list = lappend(fdw_private_list, fdw_private);
         i++;
     }
     node->fdwPrivLists = fdw_private_list;
     node->fdwDirectModifyPlans = direct_modify_plans;
 
     return node;
 }

三、跟踪分析

插入测试数据:

testdb=# insert into t_insert values(1000,'I am test','I am test','I am test');
(挂起)

启动gdb,跟踪调试:

standard_planner

[root@localhost ~]# gdb -p 1610
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-100.el7
Copyright (C) 2013 Free Software Foundation, Inc.
...
#跟踪进入subquery_planner(见后)
(gdb) n
409   final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
(gdb) 
410   best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
#最优路径,INSERT语句,Plan为T_ModifyTable
(gdb) p *best_path
$51 = {type = T_ModifyTablePath, pathtype = T_ModifyTable, parent = 0x21c40a0, pathtarget = 0x21c42b0, 
  param_info = 0x0, parallel_aware = false, parallel_safe = false, parallel_workers = 0, rows = 1, 
  startup_cost = 0, total_cost = 0.01, pathkeys = 0x0}
(gdb) 
412   top_plan = create_plan(root, best_path);
(gdb) step
create_plan (root=0x21c2cb0, best_path=0x219dd88) at createplan.c:323
323   root->curOuterRels = NULL;
(gdb) n
324   root->curOuterParams = NIL;
(gdb) 
327   plan = create_plan_recurse(root, best_path, CP_EXACT_TLIST);
(gdb) 
336   if (!IsA(plan, ModifyTable))
#plan可用于后续的执行
(gdb) p *plan
$53 = {type = T_ModifyTable, startup_cost = 0, total_cost = 0.01, plan_rows = 1, plan_width = 298, 
  parallel_aware = false, parallel_safe = false, plan_node_id = 0, targetlist = 0x0, qual = 0x0, 
  lefttree = 0x0, righttree = 0x0, initPlan = 0x0, extParam = 0x0, allParam = 0x0}

subquery_planner

[root@localhost ~]# gdb -p 1610
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-100.el7
Copyright (C) 2013 Free Software Foundation, Inc.
...
(gdb) b subquery_planner
Breakpoint 1 at 0x76a0bb: file planner.c, line 606.
(gdb) c
Continuing.
...
Breakpoint 1, subquery_planner (glob=0x21c2c20, parse=0x219de98, parent_root=0x0, hasRecursion=false, 
    tuple_fraction=0) at planner.c:606
606     root = makeNode(PlannerInfo);
#输入参数
#1,glob
(gdb) p *glob
$1 = {type = T_PlannerGlobal, boundParams = 0x0, subplans = 0x0, subroots = 0x0, rewindPlanIDs = 0x0, 
  finalrtable = 0x0, finalrowmarks = 0x0, resultRelations = 0x0, nonleafResultRelations = 0x0, 
  rootResultRelations = 0x0, relationOids = 0x0, invalItems = 0x0, paramExecTypes = 0x0, lastPHId = 0, 
  lastRowMarkId = 0, lastPlanNodeId = 0, transientPlan = false, dependsOnRole = false, 
  parallelModeOK = false, parallelModeNeeded = false, maxParallelHazard = 117 'u'}
#2,parse
#Query结构体
(gdb) p *parse
$2 = {type = T_Query, commandType = CMD_INSERT, querySource = QSRC_ORIGINAL, queryId = 0, canSetTag = true, 
  utilityStmt = 0x0, resultRelation = 1, hasAggs = false, hasWindowFuncs = false, hasTargetSRFs = false, 
  hasSubLinks = false, hasDistinctOn = false, hasRecursive = false, hasModifyingCTE = false, 
  hasForUpdate = false, hasRowSecurity = false, cteList = 0x0, rtable = 0x219e2b8, jointree = 0x21c2aa0, 
  targetList = 0x21c2b20, override = OVERRIDING_NOT_SET, onConflict = 0x0, returningList = 0x0, 
  groupClause = 0x0, groupingSets = 0x0, havingQual = 0x0, windowClause = 0x0, distinctClause = 0x0, 
  sortClause = 0x0, limitOffset = 0x0, limitCount = 0x0, rowMarks = 0x0, setOperations = 0x0, 
  constraintDeps = 0x0, withCheckOptions = 0x0, stmt_location = 0, stmt_len = 69}
#targetList中的元素为TargetEntry *
#在insert语句中,是数据表列
(gdb) p *(parse->targetList)
$3 = {type = T_List, length = 4, head = 0x21c2b00, tail = 0x21c2b90}
(gdb)  p *((TargetEntry *)(parse->targetList->head->data.ptr_value))
$4 = {xpr = {type = T_TargetEntry}, expr = 0x219e5e8, resno = 1, resname = 0x219e338 "id", 
  ressortgroupref = 0, resorigtbl = 0, resorigcol = 0, resjunk = false}
#rtable中的元素是RangeTblEntry *
#在insert操作中,是数据表
(gdb)  p *(parse->rtable)
$5 = {type = T_List, length = 1, head = 0x219e298, tail = 0x219e298}
(gdb) p *((RangeTblEntry *)(parse->rtable->head->data.ptr_value))
$6 = {type = T_RangeTblEntry, rtekind = RTE_RELATION, relid = 26731, relkind = 114 'r', tablesample = 0x0, 
  subquery = 0x0, security_barrier = false, jointype = JOIN_INNER, joinaliasvars = 0x0, functions = 0x0, 
  funcordinality = false, tablefunc = 0x0, values_lists = 0x0, ctename = 0x0, ctelevelsup = 0, 
  self_reference = false, coltypes = 0x0, coltypmods = 0x0, colcollations = 0x0, enrname = 0x0, 
  enrtuples = 0, alias = 0x0, eref = 0x219e0b8, lateral = false, inh = false, inFromCl = false, 
  requiredPerms = 1, checkAsUser = 0, selectedCols = 0x0, insertedCols = 0x21c2938, updatedCols = 0x0, 
  securityQuals = 0x0}
(gdb) p *(((RangeTblEntry *)(parse->rtable->head->data.ptr_value))->insertedCols)
$7 = {nwords = 1, words = 0x21c293c}
#3,parent_root
(gdb) p *parent_root
Cannot access memory at address 0x0
#4,hasRecursion
(gdb) p hasRecursion
$9 = false
#5,tuple_fraction
(gdb) p tuple_fraction
$10 = 0
...
639     if (parse->cteList)
(gdb) 
648     if (parse->hasSubLinks)
(gdb) 
656     inline_set_returning_functions(root);
(gdb) 
662     pull_up_subqueries(root);
(gdb) 
670     if (parse->setOperations)
(gdb) 
680     root->hasJoinRTEs = false;
(gdb) 
681     root->hasLateralRTEs = false;
682     hasOuterJoins = false;
(gdb) 
683     foreach(l, parse->rtable)
(gdb) 
685         RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
(gdb) 
687         if (rte->rtekind == RTE_JOIN)
(gdb) p *rte
$11 = {type = T_RangeTblEntry, rtekind = RTE_RELATION, relid = 26731, relkind = 114 'r', tablesample = 0x0, 
  subquery = 0x0, security_barrier = false, jointype = JOIN_INNER, joinaliasvars = 0x0, functions = 0x0, 
  funcordinality = false, tablefunc = 0x0, values_lists = 0x0, ctename = 0x0, ctelevelsup = 0, 
  self_reference = false, coltypes = 0x0, coltypmods = 0x0, colcollations = 0x0, enrname = 0x0, 
  enrtuples = 0, alias = 0x0, eref = 0x219e0b8, lateral = false, inh = false, inFromCl = false, 
  requiredPerms = 1, checkAsUser = 0, selectedCols = 0x0, insertedCols = 0x21c2938, updatedCols = 0x0, 
  securityQuals = 0x0}
...
731     parse->targetList = (List *)
(gdb) 
736     if (parse->hasTargetSRFs)
(gdb) p *((TargetEntry *)(parse->targetList->head->data.ptr_value))
$12 = {xpr = {type = T_TargetEntry}, expr = 0x21c3110, resno = 1, resname = 0x219e338 "id", 
  ressortgroupref = 0, resorigtbl = 0, resorigcol = 0, resjunk = false}
(gdb) p *(((TargetEntry *)(parse->targetList->head->data.ptr_value))->expr)
$13 = {type = T_Const}
...
#进入grouping_planner函数,此函数生成root->upper_rels & upper_targets
#注意upper_rels,grouping_planner函数执行完毕,upper_rels最后一个元素会填入相应的值
(gdb) p *root
$22 = {type = T_PlannerInfo, parse = 0x219de98, glob = 0x21c2c20, query_level = 1, parent_root = 0x0, 
  plan_params = 0x0, outer_params = 0x0, simple_rel_array = 0x0, simple_rel_array_size = 0, 
  simple_rte_array = 0x0, all_baserels = 0x0, nullable_baserels = 0x0, join_rel_list = 0x0, 
  join_rel_hash = 0x0, join_rel_level = 0x0, join_cur_level = 0, init_plans = 0x0, cte_plan_ids = 0x0, 
  multiexpr_params = 0x0, eq_classes = 0x0, canon_pathkeys = 0x0, left_join_clauses = 0x0, 
  right_join_clauses = 0x0, full_join_clauses = 0x0, join_info_list = 0x0, append_rel_list = 0x0, 
  rowMarks = 0x0, placeholder_list = 0x0, fkey_list = 0x0, query_pathkeys = 0x0, group_pathkeys = 0x0, 
  window_pathkeys = 0x0, distinct_pathkeys = 0x0, sort_pathkeys = 0x0, part_schemes = 0x0, 
  initial_rels = 0x0, upper_rels = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, upper_targets = {0x0, 0x0, 0x0, 0x0, 
    0x0, 0x0, 0x0}, processed_tlist = 0x0, grouping_map = 0x0, minmax_aggs = 0x0, planner_cxt = 0x219cde0, 
  total_table_pages = 0, tuple_fraction = 0, limit_tuples = 0, qual_security_level = 0, 
  inhTargetKind = INHKIND_NONE, hasJoinRTEs = false, hasLateralRTEs = false, hasDeletedRTEs = false, 
  hasHavingQual = false, hasPseudoConstantQuals = false, hasRecursion = false, wt_param_id = -1, 
  non_recursive_path = 0x0, curOuterRels = 0x0, curOuterParams = 0x0, join_search_private = 0x0, 
  partColsUpdated = false}
(gdb) p inheritance_update
$23 = false
(gdb) p inheritance_update
$24 = false
(gdb) p tuple_fraction
$25 = 0
(gdb) 
...
(gdb) 
1808      tlist = preprocess_targetlist(root);
(gdb) p *root
$27 = {type = T_PlannerInfo, parse = 0x219de98, glob = 0x21c2c20, query_level = 1, parent_root = 0x0, 
  plan_params = 0x0, outer_params = 0x0, simple_rel_array = 0x0, simple_rel_array_size = 0, 
  simple_rte_array = 0x0, all_baserels = 0x0, nullable_baserels = 0x0, join_rel_list = 0x0, 
  join_rel_hash = 0x0, join_rel_level = 0x0, join_cur_level = 0, init_plans = 0x0, cte_plan_ids = 0x0, 
  multiexpr_params = 0x0, eq_classes = 0x0, canon_pathkeys = 0x0, left_join_clauses = 0x0, 
  right_join_clauses = 0x0, full_join_clauses = 0x0, join_info_list = 0x0, append_rel_list = 0x0, 
  rowMarks = 0x0, placeholder_list = 0x0, fkey_list = 0x0, query_pathkeys = 0x0, group_pathkeys = 0x0, 
  window_pathkeys = 0x0, distinct_pathkeys = 0x0, sort_pathkeys = 0x0, part_schemes = 0x0, 
  initial_rels = 0x0, upper_rels = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, upper_targets = {0x0, 0x0, 0x0, 0x0, 
    0x0, 0x0, 0x0}, processed_tlist = 0x21c39e0, grouping_map = 0x0, minmax_aggs = 0x0, 
  planner_cxt = 0x219cde0, total_table_pages = 0, tuple_fraction = 0, limit_tuples = 0, 
  qual_security_level = 0, inhTargetKind = INHKIND_NONE, hasJoinRTEs = false, hasLateralRTEs = false, 
  hasDeletedRTEs = false, hasHavingQual = false, hasPseudoConstantQuals = false, hasRecursion = false, 
  wt_param_id = -1, non_recursive_path = 0x0, curOuterRels = 0x0, curOuterParams = 0x0, 
  join_search_private = 0x0, partColsUpdated = false}
#processed_tlist中的元素为TargetEntry *,也就是字段Column
(gdb) p *(root->processed_tlist)
$28 = {type = T_List, length = 4, head = 0x21c39c0, tail = 0x21c3a50}
(gdb) p *(root->processed_tlist->head)
$29 = {data = {ptr_value = 0x21c30c0, int_value = 35401920, oid_value = 35401920}, next = 0x21c3a10}
(gdb) p *(TargetEntry *)(root->processed_tlist->head.data->ptr_value)
$30 = {xpr = {type = T_TargetEntry}, expr = 0x21c3110, resno = 1, resname = 0x219e338 "id", 
  ressortgroupref = 0, resorigtbl = 0, resorigcol = 0, resjunk = false}
...
2026      root->upper_targets[UPPERREL_FINAL] = final_target;
(gdb) 
2027      root->upper_targets[UPPERREL_WINDOW] = sort_input_target;
(gdb) 
2028      root->upper_targets[UPPERREL_GROUP_AGG] = grouping_target;
(gdb) 
2035      if (have_grouping)
(gdb) p *final_target
$45 = {type = T_PathTarget, exprs = 0x21c3ee0, sortgrouprefs = 0x21c3ea0, cost = {startup = 0, 
...
(gdb) 
2197          create_modifytable_path(root, final_rel,
(gdb) 
2200                      parse->resultRelation,
(gdb) p *root
$49 = {type = T_PlannerInfo, parse = 0x219de98, glob = 0x21c2c20, query_level = 1, parent_root = 0x0, 
  plan_params = 0x0, outer_params = 0x0, simple_rel_array = 0x0, simple_rel_array_size = 0, 
  simple_rte_array = 0x0, all_baserels = 0x0, nullable_baserels = 0x0, join_rel_list = 0x21c3cf0, 
  join_rel_hash = 0x0, join_rel_level = 0x0, join_cur_level = 0, init_plans = 0x0, cte_plan_ids = 0x0, 
  multiexpr_params = 0x0, eq_classes = 0x0, canon_pathkeys = 0x0, left_join_clauses = 0x0, 
  right_join_clauses = 0x0, full_join_clauses = 0x0, join_info_list = 0x0, append_rel_list = 0x0, 
  rowMarks = 0x0, placeholder_list = 0x0, fkey_list = 0x0, query_pathkeys = 0x0, group_pathkeys = 0x0, 
  window_pathkeys = 0x0, distinct_pathkeys = 0x0, sort_pathkeys = 0x0, part_schemes = 0x0, 
  initial_rels = 0x0, upper_rels = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21c4320}, upper_targets = {0x0, 0x0, 
    0x21c3e50, 0x21c3e50, 0x0, 0x0, 0x21c3e50}, processed_tlist = 0x21c39e0, grouping_map = 0x0, 
  minmax_aggs = 0x0, planner_cxt = 0x219cde0, total_table_pages = 0, tuple_fraction = 0, limit_tuples = -1, 
  qual_security_level = 0, inhTargetKind = INHKIND_NONE, hasJoinRTEs = false, hasLateralRTEs = false, 
  hasDeletedRTEs = false, hasHavingQual = false, hasPseudoConstantQuals = false, hasRecursion = false, 
  wt_param_id = -1, non_recursive_path = 0x0, curOuterRels = 0x0, curOuterParams = 0x0, 
  join_search_private = 0x0, partColsUpdated = false}
(gdb) finish
Run till exit from #0  grouping_planner (root=0x21c2cb0, inheritance_update=false, tuple_fraction=0)
    at planner.c:2200
subquery_planner (glob=0x21c2c20, parse=0x219de98, parent_root=0x0, hasRecursion=false, tuple_fraction=0)
    at planner.c:972
#退出grouping_planner函数
...
#最终的返回值
#INSERT VALUES语句相对比较简单,没有复杂的JOIN/WITH/HAVING/GROUP等语句,这里只是简单的返回一个root节点
(gdb) p *root
$17 = {type = T_PlannerInfo, parse = 0x219de98, glob = 0x21c2c20, query_level = 1, parent_root = 0x0, 
  plan_params = 0x0, outer_params = 0x0, simple_rel_array = 0x0, simple_rel_array_size = 0, 
  simple_rte_array = 0x0, all_baserels = 0x0, nullable_baserels = 0x0, join_rel_list = 0x21c3cf0, 
  join_rel_hash = 0x0, join_rel_level = 0x0, join_cur_level = 0, init_plans = 0x0, cte_plan_ids = 0x0, 
  multiexpr_params = 0x0, eq_classes = 0x0, canon_pathkeys = 0x0, left_join_clauses = 0x0, 
  right_join_clauses = 0x0, full_join_clauses = 0x0, join_info_list = 0x0, append_rel_list = 0x0, 
  rowMarks = 0x0, placeholder_list = 0x0, fkey_list = 0x0, query_pathkeys = 0x0, group_pathkeys = 0x0, 
  window_pathkeys = 0x0, distinct_pathkeys = 0x0, sort_pathkeys = 0x0, part_schemes = 0x0, 
  initial_rels = 0x0, upper_rels = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21c4320}, upper_targets = {0x0, 0x0, 
    0x21c3e50, 0x21c3e50, 0x0, 0x0, 0x21c3e50}, processed_tlist = 0x21c39e0, grouping_map = 0x0, 
  minmax_aggs = 0x0, planner_cxt = 0x219cde0, total_table_pages = 0, tuple_fraction = 0, limit_tuples = -1, 
  qual_security_level = 0, inhTargetKind = INHKIND_NONE, hasJoinRTEs = false, hasLateralRTEs = false, 
  hasDeletedRTEs = false, hasHavingQual = false, hasPseudoConstantQuals = false, hasRecursion = false, 
  wt_param_id = -1, non_recursive_path = 0x0, curOuterRels = 0x0, curOuterParams = 0x0, 
  join_search_private = 0x0, partColsUpdated = false}

四、小结

1.重要的数据结构:PlannedStmt/PlannerGlobal/PlannerInfo/RelOptInfo/Path
2.重要的函数:subquery_planner/grouping_planner/create_plan

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯