因此,你可以把这个字段看做是一个约束字段,在做更新操作时需要判断。
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(可能有多个索引要处理)中。