389 lines
16 KiB
C++
389 lines
16 KiB
C++
/* -------------------------------------------------------------------------
|
||
*
|
||
* execScan.cpp
|
||
* 此代码提供对广义关系扫描的支持。ExecScan被传递一个节点和一个指向函数的指针,
|
||
* 以“做正确的事情”并从关系中返回一个元组。
|
||
* ExecScan然后做一些乏味的工作——检查资格并适当地投影元组。
|
||
*
|
||
* 部分版权所有(c)2020华为技术有限公司有限公司。
|
||
* 部分版权所有(c)1996-2012,PostgreSQL 全球开发集团
|
||
* 部分版权所有(c)1994,加州大学董事会
|
||
*
|
||
*
|
||
* IDENTIFICATION
|
||
* src/gausskernel/runtime/executor/execScan.cpp
|
||
*
|
||
* -------------------------------------------------------------------------
|
||
*/
|
||
#include "postgres.h"
|
||
#include "knl/knl_variable.h"
|
||
|
||
#include "executor/executor.h"
|
||
#include "miscadmin.h"
|
||
#include "utils/memutils.h"
|
||
|
||
/*
|
||
* ExecScanFetch -- 获取下一个潜在元组
|
||
*
|
||
* 如果我们在EvalPlanQual复查中,这个例程涉及替换测试元组。
|
||
* 如果我们不在,只执行访问方法的下一个元组例程。
|
||
*/
|
||
static TupleTableSlot* ExecScanFetch(ScanState* node, ExecScanAccessMtd access_mtd, ExecScanRecheckMtd recheck_mtd)
|
||
/*
|
||
* 此函数负责从扫描中获取下一个元组。它接受一个ScanState对象,
|
||
* 该对象包含有关扫描的信息,以及两个函数指针:access_mtd和recheck_mtd。
|
||
* access_mtd是指向负责访问元组数据的函数的指针。它接收一个指向扫描状态对象的指针,以及一个指向已提取元组的指针,
|
||
* 并返回一个布尔值,指示元组数据是否已成功访问。
|
||
* recheck_mtd是指向一个函数的指针,该函数负责重新检查已经提取的元组上的扫描条件。
|
||
* 它接收一个指向扫描状态对象的指针,以及一个指向需要重新检查的元组的指针,
|
||
* 并返回一个布尔值,指示元组是否仍然满足扫描条件。
|
||
* 该函数返回一个TupleTableSlot对象,该对象包含提取的元组数据。如果没有更多的元组可获取,则返回NULL。
|
||
*/
|
||
|
||
{
|
||
EState* estate = node->ps.state;
|
||
|
||
if (estate->es_epqTuple != NULL) {
|
||
/*
|
||
* 我们正在进行EvalPlanQual复查。
|
||
* 在重新检查任何特定于访问方法的条件后,返回测试元组(如果有)。
|
||
*/
|
||
Index scan_rel_id = ((Scan*)node->ps.plan)->scanrelid;
|
||
|
||
Assert(scan_rel_id > 0);
|
||
if (estate->es_epqTupleSet[scan_rel_id - 1]) {
|
||
TupleTableSlot* slot = node->ss_ScanTupleSlot;
|
||
|
||
/* 如果我们已经返回了元组,则返回空槽 */
|
||
if (estate->es_epqScanDone[scan_rel_id - 1])
|
||
return ExecClearTuple(slot);如果我们没有测试元组,则返回空槽
|
||
/* 否则请记住,我们不应该再回来了 */
|
||
estate->es_epqScanDone[scan_rel_id - 1] = true;
|
||
|
||
/* 如果我们没有测试元组,则返回空槽 */
|
||
if (estate->es_epqTuple[scan_rel_id - 1] == NULL)
|
||
return ExecClearTuple(slot);
|
||
|
||
/* 将测试元组存储在计划节点的扫描槽中 */
|
||
(void)ExecStoreTuple(estate->es_epqTuple[scan_rel_id - 1], slot, InvalidBuffer, false);
|
||
|
||
/* 检查是否符合访问方法条件 */
|
||
if (!(*recheck_mtd)(node, slot))
|
||
(void)ExecClearTuple(slot); /* 不会通过扫描返回 */
|
||
|
||
return slot;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 运行特定于节点类型的访问方法函数以获取下一个元组
|
||
*/
|
||
return (*access_mtd)(node);
|
||
}
|
||
|
||
/* ----------------------------------------------------------------
|
||
* ExecScan
|
||
*
|
||
* 使用指示的“访问方法”扫描关系,并按全局变量ExecDirection中指定的方向返回下一个符合条件的元组。
|
||
* access方法返回下一个元组,execScan()负责根据qual子句检查返回的元组。
|
||
*
|
||
* 还必须提供一个“重新检查方法”,该方法可以根据访问方法内部实现的任何qual条件检查关系的任意元组。
|
||
*
|
||
* 条件:
|
||
* -- AMI维护的“游标”位于先前返回的元组处。
|
||
*
|
||
* 初始状态:
|
||
* -- 所指示的关系被打开进行扫描,以便“光标”位于第一个符合条件的元组之前
|
||
* ----------------------------------------------------------------
|
||
*/
|
||
TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* 返回元组的函数 */
|
||
ExecScanRecheckMtd recheck_mtd)
|
||
/* 此函数负责扫描关系并返回下一个匹配的元组。它接受一个ScanState对象,该对象包含有关扫描的信息,以及两个函数指针:access_mtd和recheck_mtd。
|
||
* access_mtd是指向负责访问元组数据的函数的指针。它接收一个指向扫描状态对象的指针,以及一个指向已提取元组的指针,
|
||
* 并返回一个布尔值,指示元组数据是否已成功访问。
|
||
* recheck_mtd是指向一个函数的指针,该函数负责重新检查已经提取的元组上的扫描条件。
|
||
* 它接收一个指向扫描状态对象的指针,以及一个指向需要重新检查的元组的指针,并返回一个布尔值,指示元组是否仍然满足扫描条件。
|
||
* 该函数返回一个TupleTableSlot对象,该对象包含提取的元组数据。如果没有更多的元组可获取,则返回NULL。
|
||
*/
|
||
|
||
{
|
||
ExprContext* econtext = NULL;
|
||
List* qual = NIL;
|
||
ProjectionInfo* proj_info = NULL;
|
||
ExprDoneCond is_done;
|
||
TupleTableSlot* result_slot = NULL;
|
||
|
||
if (node->isPartTbl && !PointerIsValid(node->partitions))
|
||
return NULL;
|
||
|
||
/*
|
||
* 从节点获取数据
|
||
*/
|
||
qual = node->ps.qual;
|
||
proj_info = node->ps.ps_ProjInfo;
|
||
econtext = node->ps.ps_ExprContext;
|
||
|
||
/*
|
||
* 如果我们既没有要检查的qual,也没有要做的投影,只需跳过所有开销并返回原始扫描元组。
|
||
*/
|
||
if (qual == NULL && proj_info == NULL) {
|
||
ResetExprContext(econtext);
|
||
return ExecScanFetch(node, access_mtd, recheck_mtd);
|
||
}
|
||
|
||
/*
|
||
* 检查我们是否仍在从上一个扫描元组中投影出元组
|
||
*(因为在投影表达式中有一个函数返回集)。
|
||
* u如果是,试着投影另一个。
|
||
*/
|
||
if (node->ps.ps_TupFromTlist) {
|
||
Assert(proj_info); /* 如果不投影就不能到达这里 */
|
||
result_slot = ExecProject(proj_info, &is_done);
|
||
if (is_done == ExprMultipleResult)
|
||
return result_slot;
|
||
/* 已完成该源元组... */
|
||
node->ps.ps_TupFromTlist = false;
|
||
}
|
||
|
||
/*
|
||
* @hdfs
|
||
* 使用信息约束优化扫描bu。
|
||
* 如果isscanfalse为true,则迭代结束。
|
||
*/
|
||
if (node->is_scan_end) {
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
* 重置每个元组内存上下文以释放在上一个元组周期中分配的任何表达式求值存储。
|
||
* 请注意,在我们完成从扫描元组中投影出元组之前,这是不可能发生的。
|
||
*/
|
||
ResetExprContext(econtext);
|
||
|
||
/*
|
||
* 从access方法获取一个元组。循环直到我们获得一个通过资格的元组。
|
||
*/
|
||
for (;;) {
|
||
TupleTableSlot* slot = NULL;
|
||
|
||
CHECK_FOR_INTERRUPTS();
|
||
|
||
slot = ExecScanFetch(node, access_mtd, recheck_mtd);
|
||
/* 刷新qual每个循环 */
|
||
qual = node->ps.qual;
|
||
/*
|
||
* 如果accessMtd返回的槽包含NULL,那么这意味着没有更多的东西可以扫描,
|
||
* 所以我们只返回一个空槽,小心使用投影结果槽,这样它就有了正确的tupleDesc。
|
||
*/
|
||
if (TupIsNull(slot) || unlikely(executorEarlyStop())) {
|
||
if (proj_info != NULL)
|
||
return ExecClearTuple(proj_info->pi_slot);
|
||
else
|
||
return slot;
|
||
}
|
||
|
||
/*
|
||
* 将当前元组放入expr上下文
|
||
*/
|
||
econtext->ecxt_scantuple = slot;
|
||
|
||
/*
|
||
* 检查当前元组是否满足qual子句
|
||
*
|
||
* 在此处检查非nil qual,以避免在qual为nil时调用ExecQual()函数...只节省了几个周期,但它们加起来
|
||
* ...
|
||
*/
|
||
if (qual == NULL || ExecQual(qual, econtext, false)) {
|
||
/*
|
||
* 找到一个令人满意的扫描元组。
|
||
*/
|
||
if (proj_info != NULL) {
|
||
/*
|
||
* 形成一个投影元组,将其存储在结果元组槽中并返回它——除非我们发现我们不能从这个扫描元组中投影任何元组,
|
||
* 在这种情况下,继续扫描。
|
||
*/
|
||
result_slot = ExecProject(proj_info, &is_done);
|
||
#ifdef PGXC
|
||
/* 复制xcnodeoid(如果底层扫描的插槽有一个) */
|
||
result_slot->tts_xcnodeoid = slot->tts_xcnodeoid;
|
||
#endif /* PGXC */
|
||
if (is_done != ExprEndResult) {
|
||
node->ps.ps_TupFromTlist = (is_done == ExprMultipleResult);
|
||
|
||
/*
|
||
* @hdfs
|
||
* 使用信息约束优化外部扫描。
|
||
*/
|
||
if (IsA(node->ps.plan, ForeignScan)) {
|
||
ForeignScan* foreign_scan = (ForeignScan*)(node->ps.plan);
|
||
if (foreign_scan->scan.scan_qual_optimized) {
|
||
/*
|
||
* 如果我们找到一个合适的元组,那么set is_scan_end值为true。
|
||
* 这意味着我们在下一次迭代中没有找到合适的元组,迭代结束了。
|
||
*/
|
||
node->is_scan_end = true;
|
||
}
|
||
}
|
||
return result_slot;
|
||
}
|
||
} else {
|
||
/*
|
||
* 使用信息约束优化外部扫描。
|
||
*/
|
||
if (IsA(node->ps.plan, ForeignScan)) {
|
||
ForeignScan* foreign_scan = (ForeignScan*)(node->ps.plan);
|
||
if (foreign_scan->scan.scan_qual_optimized) {
|
||
/*
|
||
* 如果我们找到一个合适的元组,那么set is_scan_end值为true。
|
||
* 这意味着我们在下一次迭代中没有找到合适的元组,迭代结束了。
|
||
*/
|
||
node->is_scan_end = true;
|
||
}
|
||
}
|
||
/*
|
||
* 在这里,我们不投影,所以只返回扫描元组。
|
||
*/
|
||
return slot;
|
||
}
|
||
} else
|
||
InstrCountFiltered1(node, 1);
|
||
|
||
/*
|
||
* 元组无法通过qual,请释放每个元组的内存,然后重试。
|
||
*/
|
||
ResetExprContext(econtext);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ExecAssignScanProjectionInfo
|
||
* 如有必要,为扫描节点设置投影信息。
|
||
*
|
||
* 如果请求的tlist与底层元组类型完全匹配,我们可以避免投影步骤。
|
||
* 如果是,我们只需将ps_ProjegInfo设置为NULL。
|
||
* 请注意,这种情况不仅发生在简单的“SELECT*FROM…”中,而且发生在扫描节点上方有联接或其他处理节点的大多数情况下,
|
||
* 因为计划器将优先生成匹配的tlist。
|
||
*
|
||
* 必须已调用ExecAssignScanType
|
||
*/
|
||
void ExecAssignScanProjectionInfo(ScanState* node)
|
||
{
|
||
Scan* scan = (Scan*)node->ps.plan;
|
||
Index var_no;
|
||
|
||
/* 仅索引扫描的tlist中的变量应为index_VAR */
|
||
if (IsA(scan, IndexOnlyScan))
|
||
var_no = INDEX_VAR;
|
||
else
|
||
var_no = scan->scanrelid;
|
||
|
||
if (tlist_matches_tupdesc(&node->ps, scan->plan.targetlist, var_no, node->ss_ScanTupleSlot->tts_tupleDescriptor))
|
||
node->ps.ps_ProjInfo = NULL;
|
||
else
|
||
ExecAssignProjectionInfo(&node->ps, node->ss_ScanTupleSlot->tts_tupleDescriptor);
|
||
}
|
||
/* 函数ExecAssignScanProjectionInfo负责将投影信息分配给ScanState节点。让我们分解代码:
|
||
该函数采用ScanState指针作为输入。
|
||
它使用(Scan*)node->ps.plan将ScanState强制转换为Scan节点。
|
||
它声明了一个索引变量var_no。
|
||
如果扫描是仅索引扫描(使用IsA(scan,IndexOnlyScan)进行检查),则会将var_no设置为index_var。
|
||
否则,它将var_no设置为scan->scanrelid,表示扫描关系标识符。
|
||
它使用tlist_matches_tupdesc函数检查扫描的目标列表是否与扫描元组槽的元组描述符匹配。如果它们匹配,它会将node->ps.ps_ProjInfo设置为NULL。
|
||
如果目标列表和元组描述符不匹配,则调用ExecAssignProjectionInfo,使用扫描元组槽的元组描述符将投影信息分配给ScanState节点。
|
||
总之,此函数根据扫描类型以及目标列表和元组描述符之间的匹配来确定是否需要将投影信息分配给ScanState节点。
|
||
*/
|
||
|
||
/*
|
||
* ExecAssignScanProjectionInfoWithVarno
|
||
* As above, but caller can specify varno expected in Vars in the tlist.
|
||
* This function is called by ExecInitExtensiblePlan to initialize projection info.
|
||
* Usually the caller provides a targetlist describing the scan tuples, so we can
|
||
* avoid a projection step by setting ps_ProjInfo to NULL. Such as "SELECT * FROM ...".
|
||
*/
|
||
void ExecAssignScanProjectionInfoWithVarno(ScanState* node, Index var_no)
|
||
{
|
||
Scan* scan = (Scan*)node->ps.plan;
|
||
|
||
if (tlist_matches_tupdesc(&node->ps, scan->plan.targetlist, var_no, node->ss_ScanTupleSlot->tts_tupleDescriptor))
|
||
node->ps.ps_ProjInfo = NULL;
|
||
else
|
||
ExecAssignProjectionInfo(&node->ps, node->ss_ScanTupleSlot->tts_tupleDescriptor);
|
||
}
|
||
|
||
bool tlist_matches_tupdesc(PlanState* ps, List* tlist, Index var_no, TupleDesc tup_desc)
|
||
{
|
||
int num_attrs = tup_desc->natts;
|
||
int attr_no;
|
||
bool has_oid = false;
|
||
ListCell* tlist_item = list_head(tlist);
|
||
|
||
/* Check the tlist attributes */
|
||
for (attr_no = 1; attr_no <= num_attrs; attr_no++) {
|
||
Form_pg_attribute att_tup = tup_desc->attrs[attr_no - 1];
|
||
Var* var = NULL;
|
||
|
||
if (tlist_item == NULL)
|
||
return false; /* tlist too short */
|
||
var = (Var*)((TargetEntry*)lfirst(tlist_item))->expr;
|
||
if (var == NULL || !IsA(var, Var))
|
||
return false; /* tlist item not a Var */
|
||
/* if these Asserts fail, planner messed up */
|
||
Assert(var->varno == var_no);
|
||
Assert(var->varlevelsup == 0);
|
||
if (var->varattno != attr_no)
|
||
return false; /* out of order */
|
||
if (att_tup->attisdropped)
|
||
return false; /* table contains dropped columns */
|
||
|
||
/*
|
||
* Note: usually the Var's type should match the tupdesc exactly, but
|
||
* in situations involving unions of columns that have different
|
||
* typmods, the Var may have come from above the union and hence have
|
||
* typmod -1. This is a legitimate situation since the Var still
|
||
* describes the column, just not as exactly as the tupdesc does. We
|
||
* could change the planner to prevent it, but it'd then insert
|
||
* projection steps just to convert from specific typmod to typmod -1,
|
||
* which is pretty silly.
|
||
*/
|
||
if (var->vartype != att_tup->atttypid || (var->vartypmod != att_tup->atttypmod && var->vartypmod != -1))
|
||
return false; /* type mismatch */
|
||
|
||
tlist_item = lnext(tlist_item);
|
||
}
|
||
|
||
if (tlist_item != NULL)
|
||
return false; /* tlist too long */
|
||
|
||
/*
|
||
* If the plan context requires a particular hasoid setting, then that has
|
||
* to match, too.
|
||
*/
|
||
if (ExecContextForcesOids(ps, &has_oid) && has_oid != tup_desc->tdhasoid)
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* ExecScanReScan
|
||
*
|
||
* This must be called within the ReScan function of any plan node type
|
||
* that uses ExecScan().
|
||
*/
|
||
void ExecScanReScan(ScanState* node)
|
||
{
|
||
EState* estate = node->ps.state;
|
||
|
||
/* Stop projecting any tuples from SRFs in the targetlist */
|
||
node->ps.ps_TupFromTlist = false;
|
||
|
||
/* Rescan EvalPlanQual tuple if we're inside an EvalPlanQual recheck */
|
||
if (estate->es_epqScanDone != NULL) {
|
||
Index scan_rel_id = ((Scan*)node->ps.plan)->scanrelid;
|
||
|
||
Assert(scan_rel_id > 0);
|
||
|
||
estate->es_epqScanDone[scan_rel_id - 1] = false;
|
||
}
|
||
}
|