跟我一起读postgresql源码(十六)——Executor(查询执行模块之——control节点(下)) (2)

因此,你可以把这个字段看做是一个约束字段,在做更新操作时需要判断。

typedef struct ModifyTableState { PlanState ps; /* its first field is NodeTag */ CmdType operation; /* INSERT, UPDATE, or DELETE */ bool canSetTag; /* do we set the command tag/es_processed? */ bool mt_done; /* are we done? */ PlanState **mt_plans; /* subplans (one per target rel) */ int mt_nplans; /* number of plans in the array */ int mt_whichplan; /* which one is being executed (0..n-1) */ ResultRelInfo *resultRelInfo; /* per-subplan target relations */ List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */ EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */ bool fireBSTriggers; /* do we need to fire stmt triggers? */ OnConflictAction mt_onconflict; /* ON CONFLICT type */ List *mt_arbiterindexes; /* unique index OIDs to arbitrate * taking alt path */ TupleTableSlot *mt_existing; /* slot to store existing target tuple in */ List *mt_excludedtlist; /* the excluded pseudo relation's * tlist */ TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection * target */ } ModifyTableState;

那么对于ModifyTableState的一些字段,我们参照ModifyTable节点的解释,也能理解的差不多,这里不多说了。

下面进入正题:

ModifyTable节点的初始化由ExecInitModifyTable函数来做。该函数除了做一些基础的初始化操作外,针对我上面提到的那些字段,做了设置和初始化。说细一点的话就是:

(1)调用ExecInitNode函数对ModifyTable节点中的plans列表中的subplans节点进行初始化并将其结果保存到ModifyTableState结构的mt_plans字段中。在这一步中,同时也顺便做了这些事:验证了查询所涉及的target relations是否是合法的;打开这些target relations上的index,因为对于UPDATE/INSERT操作,我们同时还要对相应的索引进行操作(DELETE操作不删除索引,DELETE后遗留的index留给VACUUM去清理)。

(2)根据ModifyTable节点中的withCheckOptionLists字段初始化上面提到的WITH CHECK OPTION(如果存在的话)。初始化后保存在ModifyTableState结构的resultRelInfo字段的成员变量ri_WithCheckOptions和ri_WithCheckOptionExprs中。

(3)根据ModifyTable节点中的returningLists字段初始化上面提到的RETURNING子句(如果存在的话)并根据此构造返回的结果集的类型。如果returningLists字段为空,说明没有RETURNING子句。那么返回结果集的类型设置为NULL。

(4)如果存在ON CONFLICT DO UPDATE字段那么为他初始化目标列表Target List、投影条件resultRelInfo和过滤条件qual,结果保存在ModifyTableState结构的相应字段中。

(5)处理ModifyTable节点中的RowMark字段,结果保存在ModifyTableState结构的mt_arowmarks字段中。

(6)初始化junk filter。这个junk filter的由来是因为在plan阶段,我们会产生一些"中间信息"放在tuple中供Excutor使用。比如ctid,用来定位该元组放到磁盘文件的位置。但是当我们将元组写到磁盘时,我们不需要保存这个信息。那么这个信息相当于是冗余的了,我们需要用这个JunkFilter将其过滤和删除。

(7)我们知道我们在INSERT/UPDATE/DELETE时可能会涉及到trigger,这里设置trigger相关的slot,供后续函数调用。

(8)如果本ModifyTable节点不是顶层ModifyTable节点的话(上层还有ModifyTable节点),设置全局状态结构estate->es_auxmodifytables属性,做上标记。

ModifyTable节点的执行由ExecModifyTable函数执行。具体的来说:

(1)首先我们要记得可能有BEFORE STATEMENT triggers这玩意儿,顾名思义,就是要在STATEMENT执行之前执行这个trigger。如果存在,在进入正式的处理之前我们先要调用fireBSTriggers函数来处理它。

(2)接下来是一个大for循环。在这个for循环里面,程序调用ExecProcNode函数循环地从下层节点中读取元组。需要注意的是这个循环里面类似Append节点的操作,在读取完第一个subplans节点中的元组后,会依次读取后续subplan中的元组,直到全部读取完毕。我们以前说过postgres是一次读取一个元组并处理一个元组的。这里也不例外,每读取一个元组后根据操作的类型分别调用ExecInsert/ExecUpdate/ExecDelete函数去处理。

(3)有始有终,既然可能有BEFORE STATEMENT triggers,那么也可能有AFTER STATEMENT triggers,这里调用fireASTriggers函数来处理它。

那么我们应该对ExecInsert/ExecUpdate/ExecDelete函数感兴趣了。下面我们开始讨论他们。

1.ExecInsert

对于ExecInsert函数的话,主要是两件事:将元组插入到目标表target relation中同时将对应的索引项插入到相关的索引表index relations(可能有多个索引要处理)中。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdgxf.html