OpenGuass开源代码评注赛-针对Executor的代码评注 #35
|
|
@ -3,9 +3,9 @@
|
|||
* execClusterResize.cpp
|
||||
* MPPDB ClusterResizing relevant routines
|
||||
*
|
||||
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
* 部分版权 (c) 2020 华为技术有限公司
|
||||
* 部分版权所有 (c) 1996-2012,PostgreSQL 全球开发集团
|
||||
* 部分版权 (c) 1994,加州大学摄政
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* src/gausskernel/runtime/executor/execClusterResize.cpp
|
||||
|
|
@ -44,10 +44,10 @@
|
|||
|
||||
/*
|
||||
* ---------------------------------------------------------------------------------
|
||||
* *Local functions/variables declaration fields*
|
||||
* 局部函数/变量声明字段*
|
||||
* ---------------------------------------------------------------------------------
|
||||
*/
|
||||
/* delete delta table definition */
|
||||
/*删除增量表定义 */
|
||||
#define Natts_pg_delete_delta 3
|
||||
|
||||
#define Anum_pg_delete_delta_xcnodeid_and_dntableoid 1
|
||||
|
|
@ -120,12 +120,12 @@ static inline bool redis_ctid_retrive_function(const char* funcname, Oid rettype
|
|||
|
||||
|
||||
/*
|
||||
* - Brief: Record the given tuple's tupleid into pg_delete_delta table
|
||||
* - Parameter:
|
||||
* @rel: target relation of UPDATE/DELETE operation
|
||||
* @tupleid: tupleid that needs record
|
||||
* - Return:
|
||||
* no return value
|
||||
*简介:将给定元组的元组记录到pg_delete_delta表中
|
||||
* -参数:
|
||||
* @rel:更新/删除操作的目标关系
|
||||
* @tupleid:需要记录的元组
|
||||
*-返回:
|
||||
* 无返回值
|
||||
*/
|
||||
void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, const Relation deldelta_rel)
|
||||
{
|
||||
|
|
@ -134,10 +134,10 @@ void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, con
|
|||
HeapTuple tup = NULL;
|
||||
|
||||
Assert(deldelta_rel);
|
||||
/* In redistribution, table delete_delta has 3 or 2 column. */
|
||||
/*在重新分发中,表 delete_delta 有 3 列或 2 列。 */
|
||||
Assert(RelationGetDescr(deldelta_rel)->natts <= 3);
|
||||
|
||||
/* Iterate through attributes initializing nulls and values */
|
||||
/*循环访问初始化空值和值的属性 */
|
||||
for (int i = 0; i < Natts_pg_delete_delta; i++) {
|
||||
nulls[i] = false;
|
||||
values[i] = (Datum)0;
|
||||
|
|
@ -149,7 +149,7 @@ void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, con
|
|||
if (BUCKET_NODE_IS_VALID(bucketid)) {
|
||||
values[Anum_pg_delete_delta_tablebucketid_and_ctid - 1] |= ((uint64)bucketid << 48);
|
||||
}
|
||||
/* Record delta */
|
||||
/* 记录增量 */
|
||||
tup = heap_form_tuple(RelationGetDescr(deldelta_rel), values, nulls);
|
||||
(void)simple_heap_insert(deldelta_rel, tup);
|
||||
|
||||
|
|
@ -157,18 +157,18 @@ void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, con
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the relation is under cluster resizing operation
|
||||
* - Parameter:
|
||||
* @rel: relation that needs to check
|
||||
* - Return:
|
||||
* @TRUE: relation is under cluster resizing
|
||||
* @FALSE: relation is not under cluster resizing
|
||||
* - 简介:确定关系是否正在执行群集大小调整操作
|
||||
* - 参数:
|
||||
* @rel:需要检查的关系
|
||||
* - 返回:
|
||||
* @TRUE:关系正在调整集群大小
|
||||
* @FALSE: 关系未调整集群大小
|
||||
*/
|
||||
bool RelationInClusterResizing(const Relation rel)
|
||||
{
|
||||
Assert(rel != NULL);
|
||||
|
||||
/* Check relation's append_mode status */
|
||||
/*检查关系的append_mode状态 */
|
||||
if (!IsInitdb && RelationInRedistribute(rel))
|
||||
return true;
|
||||
|
||||
|
|
@ -176,18 +176,18 @@ bool RelationInClusterResizing(const Relation rel)
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the relation is under cluster resizing read only operation
|
||||
* - Parameter:
|
||||
* @rel: relation that needs to check
|
||||
* - Return:
|
||||
* @TRUE: relation is under cluster resizing read only
|
||||
* @FALSE: relation is not under cluster resizing read only
|
||||
* - 简要:确定关系是否处于集群调整只读操作下
|
||||
* - 参数:
|
||||
* @rel:需要检查的关系
|
||||
* - 返回:
|
||||
* @TRUE: 关系处于集群调整大小只读状态
|
||||
* @FALSE: 关系不处于集群调整大小只读状态
|
||||
*/
|
||||
bool RelationInClusterResizingReadOnly(const Relation rel)
|
||||
{
|
||||
Assert(rel != NULL);
|
||||
|
||||
/* Check relation's append_mode status */
|
||||
/*检查关系的append_mode状态 */
|
||||
if (!IsInitdb && RelationInRedistributeReadOnly(rel))
|
||||
return true;
|
||||
|
||||
|
|
@ -195,18 +195,18 @@ bool RelationInClusterResizingReadOnly(const Relation rel)
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the relation is under cluster resizing read only operation
|
||||
* - Parameter:
|
||||
* @rel: relation that needs to check
|
||||
* - Return:
|
||||
* @TRUE: relation is under cluster resizing endcatchup(write error)
|
||||
* @FALSE: relation is not under cluster resizing endcatchup(write error)
|
||||
* - 简要:确定关系是否处于集群调整只读操作下
|
||||
* - 参数:
|
||||
* @rel: 需要检查的关系
|
||||
* - 返回:
|
||||
* @TRUE: 关系处于群集调整大小状态endcatchup(写错误)
|
||||
* @FALSE: 关系不在群集调整大小范围内endcatchup(写错误)
|
||||
*/
|
||||
bool RelationInClusterResizingEndCatchup(const Relation rel)
|
||||
{
|
||||
Assert(rel != NULL);
|
||||
|
||||
/* Check relation's append_mode status */
|
||||
/* 检查关系的append_mode状态*/
|
||||
if (!IsInitdb && RelationInRedistributeEndCatchup(rel))
|
||||
return true;
|
||||
|
||||
|
|
@ -214,9 +214,9 @@ bool RelationInClusterResizingEndCatchup(const Relation rel)
|
|||
}
|
||||
|
||||
/*
|
||||
* @Description: check whether relation is in redistribution though range variable.
|
||||
* @in range_var: range variable which stored relation info.
|
||||
* @return: true for in redistribution.
|
||||
* @说明:通过范围变量检查关系是否在重新分配。
|
||||
* @在range_var:存储关系信息的范围变量。
|
||||
* @在重新分配中返回:true。
|
||||
*/
|
||||
bool CheckRangeVarInRedistribution(const RangeVar* range_var)
|
||||
{
|
||||
|
|
@ -228,7 +228,7 @@ bool CheckRangeVarInRedistribution(const RangeVar* range_var)
|
|||
|
||||
if (OidIsValid(relid)) {
|
||||
relation = relation_open(relid, NoLock);
|
||||
/* If the relation is index, we should check the related table is resizing or not. */
|
||||
/* 如果关系是索引,我们应该检查相关表是否在调整大小。*/
|
||||
if (RelationIsIndex(relation)) {
|
||||
Oid heapOid = IndexGetRelation(relid, false);
|
||||
Relation heapRelation = relation_open(heapOid, AccessShareLock);
|
||||
|
|
@ -245,12 +245,12 @@ bool CheckRangeVarInRedistribution(const RangeVar* range_var)
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the table name is delete_delta table.
|
||||
* - Parameter:
|
||||
* @relname: name of target table
|
||||
* - Return:
|
||||
* @TRUE: the table is delete_delta table
|
||||
* @FALSE: the table is not delete_delta table
|
||||
* - 简要:确定表名是否为delete_delta table。
|
||||
* - 参数:
|
||||
* @relname: 目标表名
|
||||
* - 返回:
|
||||
* @TRUE: 表为delete_delta表
|
||||
* @FALSE: 这个表不是delete_delta表
|
||||
*/
|
||||
bool RelationIsDeleteDeltaTable(char* delete_delta_name)
|
||||
{
|
||||
|
|
@ -292,10 +292,10 @@ bool RelationIsDeleteDeltaTable(char* delete_delta_name)
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the Progress is under cluster resizing status
|
||||
* - Return:
|
||||
* @TRUE: Progress is under cluster resizing
|
||||
* @FALSE: Progress is not under cluster resizing
|
||||
* - 简要:确定进度是否处于集群调整状态
|
||||
* - 返回:
|
||||
* @TRUE: 正在调整集群大小
|
||||
* @FALSE: 进度并不在集群调整中
|
||||
*/
|
||||
bool ClusterResizingInProgress()
|
||||
{
|
||||
|
|
@ -329,27 +329,27 @@ bool ClusterResizingInProgress()
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: get the name of delete_delta table
|
||||
* - Parameter:
|
||||
* @relname: name of target table
|
||||
* @delta_delta_name: output value for delete_delta table name
|
||||
* @isMultiCatchup: multi catchup delta or not
|
||||
* - Return:
|
||||
* no return value
|
||||
* -简介:获取delete_delta表的名称
|
||||
* - 参数:
|
||||
* @relname: 目标表名
|
||||
* @delta_delta_name: delete_delta表名的输出值
|
||||
* @isMultiCatchup: 是不是多追赶delta
|
||||
* - 返回:
|
||||
* 无返回值
|
||||
*/
|
||||
static inline void RelationGetDeleteDeltaTableName(Relation rel, char* delete_delta_name, bool isMultiCatchup)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
/* Check if output parameter it not palloc()-ed from caller side */
|
||||
/* 检查输出参数是否没有从调用方palloc()-ed */
|
||||
if (delete_delta_name == NULL || rel == NULL) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid parameter in function '%s'", __FUNCTION__)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up Relation's reloptions to get table's cnoid to
|
||||
* form the name of delete_delta table
|
||||
* 查找Relation的关联以获得表的id
|
||||
* 形成delete_delta表的名称
|
||||
*/
|
||||
if (!IsInitdb) {
|
||||
if (RelationInClusterResizing(rel) && !RelationInClusterResizingReadOnly(rel)) {
|
||||
|
|
@ -381,12 +381,12 @@ static inline void RelationGetDeleteDeltaTableName(Relation rel, char* delete_de
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: get and open delete_delta rel
|
||||
* - Parameter:
|
||||
* @rel: target relation of UPDATE/DELETE/TRUNCATE operation
|
||||
* @lockmode: lock mode
|
||||
* @isMultiCatchup: multi catchup delta or not
|
||||
* - Return:
|
||||
* - 简介:获取并打开delete_delta rel
|
||||
* - 参数:
|
||||
* @rel: UPDATE/DELETE/TRUNCATE操作的目标关系
|
||||
* @lockmode: 锁定模式
|
||||
* @isMultiCatchup: 是不是多追赶delta
|
||||
* - 返回:
|
||||
* delete_delta rel
|
||||
*/
|
||||
Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool isMultiCatchup)
|
||||
|
|
@ -403,22 +403,22 @@ Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool is
|
|||
RelationGetDeleteDeltaTableName(rel, (char*)delete_delta_tablename, isMultiCatchup);
|
||||
data_redis_namespace = get_namespace_oid("data_redis", false);
|
||||
|
||||
/* We are going to fetch the delete delta relation under data_redis schema. */
|
||||
/* 我们将在data_redis模式下获取delete delta关系。 */
|
||||
deldelta_relid = get_relname_relid(delete_delta_tablename, data_redis_namespace);
|
||||
if (!OidIsValid(deldelta_relid)) {
|
||||
/*
|
||||
* If multi catchup delta table is not there, just return NULL. We should not
|
||||
* report error, because it is a valid case. Multi catchup delta table is
|
||||
* dropped in each catchup iteration.
|
||||
* 如果多追赶增量表不存在,则返回NULL。否则不是 (We should not)
|
||||
* 报告错误,因为这是一个有效的案例。多追赶delta表是( Multi catchup delta table is)
|
||||
* 在每次追赶迭代中被丢弃。
|
||||
*/
|
||||
if (isMultiCatchup) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* To support Update or Delete during extension, we need to add 2 more columns.
|
||||
* more columns. Limited by MaxHeapAttributeNumber, if the table already contains too many columns,
|
||||
* we don't allow update or delete anymore, but insert statement can still proceed.
|
||||
* 为了在扩展期间支持更新或删除,我们需要添加2列。
|
||||
* 更多的列。如果表已经包含了太多的列,受maxheapattributennumber的限制,
|
||||
* 我们不再允许更新或删除,但插入语句仍然可以进行。
|
||||
*/
|
||||
if (((rel->rd_att->natts > (MaxHeapAttributeNumber - (Natts_pg_delete_delta - 1))) &&
|
||||
!RELATION_IS_PARTITIONED(rel)) ||
|
||||
|
|
@ -429,7 +429,7 @@ Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool is
|
|||
RelationGetRelationName(rel)),
|
||||
errdetail("Can not support online extension, if the table contains too many columns")));
|
||||
}
|
||||
/* ERROR case, should never come here */
|
||||
/* 错误情况下,不应该出现在这里 */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_TABLE),
|
||||
errmsg("delete delta table %s is not found when do cluster resizing table \"%s\"",
|
||||
|
|
@ -446,11 +446,11 @@ Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool is
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: Check the stmtment during online expansion, block unsupported ddl in cluster resizing.
|
||||
* - Parameter:
|
||||
* @rel: parsetree of DDL
|
||||
* - Return:
|
||||
* no return value
|
||||
* - 简介:检查在线扩展期间的配置,在集群调整中阻止不支持的ddl。
|
||||
* - 参数:
|
||||
* @rel: DDL的解析树
|
||||
* -返回:
|
||||
* 无返回值
|
||||
*/
|
||||
void BlockUnsupportedDDL(const Node* parsetree)
|
||||
{
|
||||
|
|
@ -466,11 +466,11 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
LOCKMODE lockmode_openrel = AccessShareLock;
|
||||
|
||||
/*
|
||||
* Check for shared-cache-inval messages before trying to access the
|
||||
* relation. This is needed to cover the case where the name
|
||||
* identifies a rel that has been dropped and recreated since the
|
||||
* start of our transaction: if we don't flush the old syscache entry,
|
||||
* then we'll latch onto that entry and suffer an error later.
|
||||
* 文件之前,请检查是否存在共享缓存无效消息
|
||||
* relation. 关系。这是需要覆盖的情况下的名称
|
||||
* 对象之后已删除并重新创建的rel
|
||||
* 事务开始:如果我们不刷新旧的syscache条目,
|
||||
* 然后我们将锁定该条目并在稍后遭受错误。
|
||||
*/
|
||||
AcceptInvalidationMessages();
|
||||
|
||||
|
|
@ -501,13 +501,13 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
return;
|
||||
} break;
|
||||
|
||||
/* Block CURSOR for while table in cluster resizing */
|
||||
/* 在集群调整大小时阻塞游标 */
|
||||
case T_PlannedStmt: {
|
||||
PlannedStmt* stmt = (PlannedStmt*)parsetree;
|
||||
relidlist = stmt->relationOids;
|
||||
} break;
|
||||
|
||||
/* Block RENAME while table in cluster resizing */
|
||||
/* 当表在集群中调整大小时,块RENAME */
|
||||
case T_RenameStmt: {
|
||||
RenameStmt* stmt = (RenameStmt*)parsetree;
|
||||
|
||||
|
|
@ -540,11 +540,11 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
stmt->relation->relname)));
|
||||
} break;
|
||||
|
||||
/* Block ALTER set schema while table in cluster resizing */
|
||||
/* 当表在集群中调整大小时,Block ALTER设置模式 */
|
||||
case T_AlterObjectSchemaStmt: {
|
||||
AlterObjectSchemaStmt* stmt = (AlterObjectSchemaStmt*)parsetree;
|
||||
|
||||
/* disable alter table set schema when transfer */
|
||||
/* 在传输时禁用alter table set schema */
|
||||
if (stmt->relation != NULL) {
|
||||
Oid relOid = RangeVarGetRelid(stmt->relation, AccessShareLock, true);
|
||||
if (OidIsValid(relOid)) {
|
||||
|
|
@ -567,7 +567,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
stmt->relation->relname)));
|
||||
} break;
|
||||
|
||||
/* Block CREATE index while table in cluster resizing(for row table only) */
|
||||
/* 当表在集群中调整大小时,阻塞创建索引(仅适用于行表) */
|
||||
case T_IndexStmt: {
|
||||
IndexStmt* stmt = (IndexStmt*)parsetree;
|
||||
if (stmt->relation) {
|
||||
|
|
@ -590,13 +590,13 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
}
|
||||
} break;
|
||||
|
||||
/* Block REINDEX while table in cluster resizing(for row table only) */
|
||||
/* 当表在集群中调整大小时,块REINDEX(仅适用于行表) */
|
||||
case T_ReindexStmt: {
|
||||
ReindexStmt* stmt = (ReindexStmt*)parsetree;
|
||||
if (stmt->relation) {
|
||||
relid = RangeVarGetRelid(stmt->relation, AccessShareLock, true);
|
||||
if (OidIsValid(relid)) {
|
||||
/* release index lock before lock table to avoid deadlock */
|
||||
/* 在锁表之前释放索引锁以避免死锁 */
|
||||
UnlockRelationOid(relid, AccessShareLock);
|
||||
|
||||
Relation relation = relation_open(relid, NoLock);
|
||||
|
|
@ -622,7 +622,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
}
|
||||
} break;
|
||||
|
||||
/* Block ALTER-Table while table in cluster resizing */
|
||||
/* 当表在集群中调整大小时,阻塞ALTER-Table */
|
||||
case T_AlterTableStmt: {
|
||||
AlterTableStmt* stmt = (AlterTableStmt*)parsetree;
|
||||
AlterTableCmd* cmd = NULL;
|
||||
|
|
@ -631,13 +631,13 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
switch (cmd->subtype) {
|
||||
case AT_TruncatePartition: {
|
||||
/*
|
||||
* We do not allow truncate partition when the target is in read only
|
||||
* mode during online expansion time.
|
||||
* 当目标处于只读状态时,我们不允许截断分区
|
||||
*在线扩容时的模式
|
||||
*/
|
||||
if (stmt->relation) {
|
||||
relid = RangeVarGetRelid(stmt->relation, lockmode_getrelid, true);
|
||||
if (OidIsValid(relid)) {
|
||||
/* disable alter table truncate partition during transfer */
|
||||
/* 禁止在传输过程中截断分区 */
|
||||
if (CheckRangeVarInRedistribution(stmt->relation)) {
|
||||
Oid nsOid = GetNamespaceIdbyRelId(relid);
|
||||
TRANSFER_DISABLE_DDL(nsOid);
|
||||
|
|
@ -704,12 +704,12 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
}
|
||||
}
|
||||
|
||||
/* If rel option contain append_mode, then not check. */
|
||||
/* 如果rel选项包含append_mode,则不检查。 */
|
||||
if (opt != NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* fall through */
|
||||
/* 失败 */
|
||||
default: {
|
||||
if (stmt->relation && !u_sess->attr.attr_sql.enable_cluster_resize &&
|
||||
CheckRangeVarInRedistribution(stmt->relation))
|
||||
|
|
@ -725,7 +725,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
return;
|
||||
} break;
|
||||
|
||||
/* Block CREATE-RULE statements while target table in cluster resizing */
|
||||
/* 当集群中的目标表调整大小时,阻塞CREATE-RULE语句 */
|
||||
case T_RuleStmt: {
|
||||
RuleStmt* stmt = (RuleStmt*)parsetree;
|
||||
if (stmt->relation) {
|
||||
|
|
@ -734,7 +734,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
}
|
||||
} break;
|
||||
|
||||
/* Block CREATE SEQUENCE set schema while owner table in cluster resizing */
|
||||
/* 当所有者表在集群中调整大小时,Block CREATE SEQUENCE设置模式 */
|
||||
case T_CreateSeqStmt: {
|
||||
CreateSeqStmt* stmt = (CreateSeqStmt*)parsetree;
|
||||
List* owned_by = NULL;
|
||||
|
|
@ -761,7 +761,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
}
|
||||
} break;
|
||||
|
||||
/* Block ALTER SEQUENCE while owner table in cluster resizing */
|
||||
/* 当集群中的所有者表调整大小时,阻塞ALTER SEQUENCE */
|
||||
case T_AlterSeqStmt: {
|
||||
AlterSeqStmt* stmt = (AlterSeqStmt*)parsetree;
|
||||
List* owned_by = NIL;
|
||||
|
|
@ -788,7 +788,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
}
|
||||
} break;
|
||||
|
||||
/* Block CLUSTER while table in cluster resizing */
|
||||
/* 当表在集群中调整大小时阻塞集群 */
|
||||
case T_ClusterStmt: {
|
||||
ClusterStmt* stmt = (ClusterStmt*)parsetree;
|
||||
if (stmt->relation && CheckRangeVarInRedistribution(stmt->relation))
|
||||
|
|
@ -799,7 +799,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
stmt->relation->relname)));
|
||||
} break;
|
||||
|
||||
/* Block VACUUM FULL while table in cluster resizing */
|
||||
/* 当表在集群中调整大小时,块真空已满 */
|
||||
case T_VacuumStmt: {
|
||||
VacuumStmt* stmt = (VacuumStmt*)parsetree;
|
||||
if ((stmt->options & VACOPT_VACUUM) || (stmt->options & VACOPT_MERGE)) {
|
||||
|
|
@ -822,7 +822,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
}
|
||||
} break;
|
||||
|
||||
/* Block truncate DDL when the target table is read only in cluster resizing */
|
||||
/* 在集群调整大小时,当目标表为只读时,块截断DDL */
|
||||
case T_TruncateStmt: {
|
||||
ListCell* cell = NULL;
|
||||
TruncateStmt* stmt = (TruncateStmt*)parsetree;
|
||||
|
|
@ -857,7 +857,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
DropStmt* stmt = (DropStmt*)parsetree;
|
||||
switch (stmt->removeType) {
|
||||
case OBJECT_TABLE: {
|
||||
/* disable drop table when transfer */
|
||||
/* 在传输时禁用drop表 */
|
||||
ListCell* cell = NULL;
|
||||
foreach (cell, stmt->objects) {
|
||||
RangeVar* rel = makeRangeVarFromNameList((List*)lfirst(cell));
|
||||
|
|
@ -871,7 +871,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
break;
|
||||
}
|
||||
case OBJECT_SCHEMA: {
|
||||
/* disable drop schema when transfer */
|
||||
/* 传输时禁用删除模式 */
|
||||
ListCell* cell = NULL;
|
||||
foreach (cell, stmt->objects) {
|
||||
List* objname = (List*)lfirst(cell);
|
||||
|
|
@ -887,7 +887,7 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
} break;
|
||||
|
||||
case T_CreateStmt: {
|
||||
/* disable create table when transfer */
|
||||
/* 禁止传输时创建表 */
|
||||
CreateStmt* stmt = (CreateStmt*)parsetree;
|
||||
if (stmt->relation != NULL) {
|
||||
Oid nsOid = RangeVarGetCreationNamespace(stmt->relation);
|
||||
|
|
@ -916,14 +916,14 @@ void BlockUnsupportedDDL(const Node* parsetree)
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: For online expanions, the shippable function is evaluated here, the module
|
||||
* will be invoked in optimizer when do FQS evaluation, we have to define function
|
||||
* as STABLE
|
||||
* - Parameter:
|
||||
* @funcid: oid of user defined function which is createed/dropped in scope of gs_redis
|
||||
* - Return:
|
||||
* @true: shippable
|
||||
* @false: unshippable
|
||||
* - 简介:对于在线扩展,这里评估的是可发布功能模块
|
||||
* 在优化器中调用FQS评估时,我们必须定义函数
|
||||
* 是稳定的(as STABLE)
|
||||
* - 参数:
|
||||
* @funcid: 在gs_redis范围内创建/删除的用户定义函数的Oid
|
||||
* - 返回:
|
||||
* @true: 可交付
|
||||
* @false: 不可交付
|
||||
*/
|
||||
bool redis_func_shippable(Oid funcid)
|
||||
{
|
||||
|
|
@ -937,11 +937,11 @@ bool redis_func_shippable(Oid funcid)
|
|||
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function with OID %u does not exist", funcid)));
|
||||
}
|
||||
|
||||
/* Fetch function signatures */
|
||||
/* 获取函数签名 */
|
||||
rettype = get_func_signature(funcid, &argstype, &nargs);
|
||||
|
||||
if (redis_tupleid_retrive_function(func_name, rettype, argstype, nargs)) {
|
||||
/* tupleid retrive functions is shippable to datanodes */
|
||||
/* Tupleid检索函数可以发布到数据节点 */
|
||||
result = true;
|
||||
} else if (redis_offset_retrive_function(func_name, rettype, argstype, nargs)) {
|
||||
result = true;
|
||||
|
|
@ -961,11 +961,11 @@ bool redis_func_shippable(Oid funcid)
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: determine if given funcid reflects a dn-stable function
|
||||
* - Parameter:
|
||||
* @funcid: function oid that to evaluate
|
||||
* - Return:
|
||||
* @result: true:dnstable false: not-dnstable function
|
||||
* - 简介:确定给定的函数是否反映了一个非稳定函数
|
||||
* - 参数:
|
||||
* @funcid: 要求值的函数oid
|
||||
* - 返回:
|
||||
* @result: true:不稳定的 false: 不稳定的函数
|
||||
*/
|
||||
bool redis_func_dnstable(Oid funcid)
|
||||
{
|
||||
|
|
@ -981,11 +981,11 @@ bool redis_func_dnstable(Oid funcid)
|
|||
errmsg("function with OID %u does not exist when checking function dnstable", funcid)));
|
||||
}
|
||||
|
||||
/* Fetch function signatures */
|
||||
/* 获取函数签名 */
|
||||
rettype = get_func_signature(funcid, &argstype, &nargs);
|
||||
|
||||
if (redis_tupleid_retrive_function(func_name, rettype, argstype, nargs)) {
|
||||
/* tupleid retrive functions is dnstable */
|
||||
/* 管状反射函数是不稳定的 */
|
||||
result = true;
|
||||
}
|
||||
|
||||
|
|
@ -993,23 +993,23 @@ bool redis_func_dnstable(Oid funcid)
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: evaluate ctid functions into a const value to avoid per-scanning
|
||||
* tuple invokation in seqscan.
|
||||
* - Parameter:
|
||||
* @rel: the rel being redistributing
|
||||
* @original_quals: the original quals possible contains ctid_funcs
|
||||
* @isRangeScanInRedis: if is a redis range scan
|
||||
* - Return:
|
||||
* @new_quals: quals which func call be replaced by a const
|
||||
* - 简介:将ctid函数求值为const值以避免每次扫描
|
||||
* 在seqscan中调用元组。
|
||||
* - 参数:
|
||||
* @rel: 真正的问题是再分配
|
||||
* @original_quals: 原始的quals可能包含ctid_funcs
|
||||
* @isRangeScanInRedis: 这是一个redis范围扫描
|
||||
* - 返回:
|
||||
* @new_quals: 函数调用的Quals将被const替换
|
||||
*/
|
||||
List* eval_ctid_funcs(Relation rel, List* original_quals, RangeScanInRedis *rangeScanInRedis)
|
||||
{
|
||||
|
||||
StringInfo qual_str = makeStringInfo();
|
||||
/*
|
||||
* we have to make a copy of the original quals, since the eval_dnstable_func_mutator
|
||||
* will modify the it. the original qual will be needed again and again in later
|
||||
* to be re-eval in partition table scans.
|
||||
* 由于eval_dnstable_func_mutator的存在,我们必须对原始的quals进行复制
|
||||
* 将修改它。在以后的时间里,将会一次又一次地需要原始的质量
|
||||
* 要在分区表扫描中重新计算。
|
||||
*/
|
||||
List* new_quals = (List*)copyObject((const void*)(original_quals));
|
||||
|
||||
|
|
@ -1033,16 +1033,16 @@ static int32 get_expr_const_val(Node *val){
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: working house for eval_dnstable_func() to evaluate dn stable function into a const
|
||||
* value to avoid per-scanning tuple invocation in seqscan
|
||||
* - Parameter:
|
||||
* @rel: the rel being redistributing
|
||||
* @node: expression node
|
||||
* @qual_str: predicate pattern
|
||||
* @isRangeScanInRedis: output to indicate if the predicate pattern is range scan in redis
|
||||
* @isRoot: we want to compare the predicate pattern only once at root level
|
||||
* - Return:
|
||||
* @result: expression tree with dn stable function const-evaluated
|
||||
* - 简介:eval_dnstable_func()的工作库,用于将一个稳定函数求值为const
|
||||
* 值以避免在seqscan中调用每次扫描的元组
|
||||
* - 参数:
|
||||
* @rel: 真正的问题是再分配
|
||||
* @node: 表达式节点
|
||||
* @qual_str: 谓词模式
|
||||
* @isRangeScanInRedis: 输出以指示谓词模式是否为redis中的范围扫描
|
||||
* @isRoot: 我们只想在根级别对谓词模式进行一次比较
|
||||
* - 返回:
|
||||
* @result: 表达式树与dn稳定函数const评估
|
||||
*/
|
||||
static Node* eval_dnstable_func_mutator(
|
||||
Relation rel, Node* node, StringInfo qual_str, RangeScanInRedis *rangeScanInRedis, bool isRoot)
|
||||
|
|
@ -1057,7 +1057,7 @@ static Node* eval_dnstable_func_mutator(
|
|||
case T_FuncExpr: {
|
||||
FuncExpr* expr = (FuncExpr*)node;
|
||||
|
||||
/* flatten dn stable function into const value */
|
||||
/* 将一个稳定函数扁平化为const值 */
|
||||
if (redis_func_dnstable(expr->funcid)) {
|
||||
Node* new_const = NULL;
|
||||
char* funcname = get_func_name(expr->funcid);
|
||||
|
|
@ -1093,8 +1093,8 @@ static Node* eval_dnstable_func_mutator(
|
|||
Node* new_expr = eval_dnstable_func_mutator(rel, expr, qual_str, rangeScanInRedis, false);
|
||||
|
||||
/*
|
||||
* If a FuncExpr node is evalated into a T_Const value, we are hitting
|
||||
* the point so replace it in qual list.
|
||||
* 如果将FuncExpr节点求值为T_Const值,则为命中
|
||||
* 将点替换为等号列表。
|
||||
*/
|
||||
if (expr && IsA(expr, FuncExpr) && new_expr && IsA(new_expr, Const)) {
|
||||
l = list_delete_ptr(l, expr);
|
||||
|
|
@ -1103,8 +1103,8 @@ static Node* eval_dnstable_func_mutator(
|
|||
}
|
||||
|
||||
/*
|
||||
* If the predicate at root is something like "where ctid between pg_get_redis_rel_start_ctid('xx')
|
||||
* and pg_get_redis_rel_end_ctid('xx')" on DN, we will pushdown the predicate at scan node.
|
||||
* 如果在根的谓词类似于“where ctid between pg_get_redis_rel_start_ctid('xx')”
|
||||
* 和pg_get_redis_rel_end_ctid('xx')"在DN上,我们将在扫描节点下推谓词。
|
||||
*/
|
||||
if (isRoot && pg_strcasecmp(qual_str->data, RANGE_SCAN_IN_REDIS) == 0) {
|
||||
rangeScanInRedis->isRangeScanInRedis = true;
|
||||
|
|
@ -1129,7 +1129,7 @@ static Node* eval_dnstable_func_mutator(
|
|||
}
|
||||
case T_Var: {
|
||||
Var* var = (Var*)node;
|
||||
/* we only expect tid column in the predicate */
|
||||
/* 我们只期望谓词中有tid列 */
|
||||
if (var->vartype == TIDOID) {
|
||||
appendStringInfoString(qual_str, "tid");
|
||||
appendStringInfoString(qual_str, "+");
|
||||
|
|
@ -1147,10 +1147,10 @@ static Node* eval_dnstable_func_mutator(
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: get and open new_table rel
|
||||
* - Parameter:
|
||||
* @rel: target relation of TRUNCATE operation
|
||||
* - Return:
|
||||
* - 简介:获取并打开new_table rel
|
||||
* - 参数:
|
||||
* @rel: TRUNCATE操作的目标关系
|
||||
* - 返回:
|
||||
* new_table rel
|
||||
*/
|
||||
Relation GetAndOpenNewTableRel(const Relation rel, LOCKMODE lockmode)
|
||||
|
|
@ -1168,7 +1168,7 @@ Relation GetAndOpenNewTableRel(const Relation rel, LOCKMODE lockmode)
|
|||
data_redis_namespace = get_namespace_oid("data_redis", false);
|
||||
newtable_relid = get_relname_relid(new_tablename, data_redis_namespace);
|
||||
if (!OidIsValid(newtable_relid)) {
|
||||
/* ERROR case, should never come here */
|
||||
/* 错误情况下,不应该出现在这里 */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||
errmsg("new table %s is not found when do cluster resizing table \"%s\"",
|
||||
|
|
@ -1185,18 +1185,18 @@ Relation GetAndOpenNewTableRel(const Relation rel, LOCKMODE lockmode)
|
|||
}
|
||||
|
||||
/*
|
||||
* - Brief: get the name of new table
|
||||
* - Parameter:
|
||||
* @relname: name of target table
|
||||
* @newtable_name: output value for new table name
|
||||
* - Return:
|
||||
* no return value
|
||||
* - 简介:获得新表的名称
|
||||
* - 参数:
|
||||
* @relname: 目标表名
|
||||
* @newtable_name: 新表名的输出值
|
||||
* - 返回:
|
||||
* 无返回值
|
||||
*/
|
||||
void RelationGetNewTableName(Relation rel, char* newtable_name)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
/* Check if output parameter it not palloc()-ed from caller side */
|
||||
/* 检查输出参数是否没有从调用方palloc()-ed */
|
||||
if (newtable_name == NULL || rel == NULL) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
|
|
@ -1204,8 +1204,8 @@ void RelationGetNewTableName(Relation rel, char* newtable_name)
|
|||
}
|
||||
|
||||
/*
|
||||
* Look up relaion's reloptions to get table's cnoid to
|
||||
* form the name of new table
|
||||
* 查找关系的关联以获得表的关联
|
||||
* 形成新表的名称
|
||||
*/
|
||||
if (!IsInitdb) {
|
||||
Oid rel_cn_oid = RelationGetRelCnOid(rel);
|
||||
|
|
@ -1216,19 +1216,19 @@ void RelationGetNewTableName(Relation rel, char* newtable_name)
|
|||
rc = snprintf_s(
|
||||
newtable_name, NAMEDATALEN, NAMEDATALEN - 1, "data_redis_tmp_%s", RelationGetRelationName(rel));
|
||||
}
|
||||
/* check the return value of security function */
|
||||
/* 检查安全函数的返回值 */
|
||||
securec_check_ss(rc, "\0", "\0");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* - Brief: Determine if the relation is under cluster resizing write error mode
|
||||
* - Parameter:
|
||||
* @rel: relation that needs to check
|
||||
* - Return:
|
||||
* @TRUE: relation is under cluster resizing write error mode
|
||||
* @FALSE: relation is not under cluster resizing write error mode
|
||||
* - 简介:确定关系是否处于群集调整大小写错误模式
|
||||
* - 参数:
|
||||
* @rel: 需要检查的关系
|
||||
* - 参数:
|
||||
* @TRUE: 关系处于群集调整大小写错误模式
|
||||
* @FALSE: 关系不在群集调整大小写错误模式下
|
||||
*/
|
||||
bool RelationInClusterResizingWriteErrorMode(const Relation rel)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
/* -------------------------------------------------------------------------
|
||||
*
|
||||
* execCurrent.c
|
||||
* executor support for WHERE CURRENT OF cursor
|
||||
* 执行程序支持WHERE CURRENT OF游标执行程序支持WHERE CURRENT OF游标
|
||||
*
|
||||
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
* 华为技术有限公司版权所有
|
||||
* 部分版权所有(c) 1996-2012, PostgreSQL全球发展集团
|
||||
* 版权所有(c) 1994,加州大学董事会
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* 识别
|
||||
* src/backend/executor/execCurrent.c
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
|
|
@ -38,14 +38,12 @@ static ScanState* search_plan_tree(PlanState *node, Oid table_oid);
|
|||
/*
|
||||
* execCurrentOf
|
||||
*
|
||||
* Given a CURRENT OF expression and the OID of a table, determine which row
|
||||
* of the table is currently being scanned by the cursor named by CURRENT OF,
|
||||
* and return the row's TID into *current_tid.
|
||||
* 给定CURRENT OF表达式和表的OID,确定哪一行
|
||||
* 当前正在被名为CURRENT of的游标扫描
|
||||
* 并返回该行的TID为*current_tid。
|
||||
*
|
||||
* Returns TRUE if a row was identified. Returns FALSE if the cursor is valid
|
||||
* for the table but is not currently scanning a row of the table (this is a
|
||||
* legal situation in inheritance cases). Raises error if cursor is not a
|
||||
* valid updatable scan of the specified table.
|
||||
* 如果一行被识别,则返回TRUE。如果游标有效,则返回FALSE
|
||||
* 但是当前没有扫描表的一行(这是继承情况下的合法情况)。如果游标不是指定表的有效可更新扫描,则引发错误。
|
||||
*/
|
||||
bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relation, ItemPointer current_tid,
|
||||
RelationPtr partitionOfCursor_tid)
|
||||
|
|
@ -55,14 +53,14 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
QueryDesc *query_desc = NULL;
|
||||
Oid table_oid = RelationGetRelid(relation);
|
||||
|
||||
/* Get the cursor name --- may have to look up a parameter reference */
|
||||
/* 获取游标名称——可能需要查找参数引用 */
|
||||
if (cexpr->cursor_name) {
|
||||
cursor_name = cexpr->cursor_name;
|
||||
} else {
|
||||
cursor_name = fetch_cursor_param_value(econtext, cexpr->cursor_param);
|
||||
}
|
||||
|
||||
/* Find the cursor's portal */
|
||||
/* 找到游标的入口 */
|
||||
portal = GetPortalByName(cursor_name);
|
||||
if (!PortalIsValid(portal)) {
|
||||
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_CURSOR),
|
||||
|
|
@ -70,8 +68,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
|
||||
/*
|
||||
* We have to watch out for non-SELECT queries as well as held cursors,
|
||||
* both of which may have null query_desc.
|
||||
* 我们必须注意非select查询和持有的游标,它们的query_desc都可能为空
|
||||
*/
|
||||
if (portal->strategy != PORTAL_ONE_SELECT) {
|
||||
ereport(ERROR,
|
||||
|
|
@ -85,26 +82,23 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
|
||||
/*
|
||||
* We have two different strategies depending on whether the cursor uses
|
||||
* FOR UPDATE/SHARE or not. The reason for supporting both is that the
|
||||
* FOR UPDATE code is able to identify a target table in many cases where
|
||||
* the other code can't, while the non-FOR-UPDATE case allows use of WHERE
|
||||
* CURRENT OF with an insensitive cursor.
|
||||
* 根据游标是否使用,我们有两种不同的策略
|
||||
* 是否更新/共享。支持两者的原因是
|
||||
* 在许多情况下,FOR UPDATE代码能够识别目标表,而其他代码不能,而非FOR-UPDATE情况允许使用不敏感游标的when CURRENT of。
|
||||
*/
|
||||
if (query_desc->estate->es_rowMarks) {
|
||||
ExecRowMark *erm = NULL;
|
||||
ListCell *lc = NULL;
|
||||
|
||||
/*
|
||||
* Here, the query must have exactly one FOR UPDATE/SHARE reference to
|
||||
* the target table, and we dig the ctid info out of that.
|
||||
* 这里,查询必须只有一个对目标表的FOR UPDATE/SHARE引用,我们从中挖掘出ctid信息。
|
||||
*/
|
||||
erm = NULL;
|
||||
foreach (lc, query_desc->estate->es_rowMarks) {
|
||||
ExecRowMark *thiserm = (ExecRowMark *)lfirst(lc);
|
||||
|
||||
if (!RowMarkRequiresRowShareLock(thiserm->markType)) {
|
||||
continue; /* ignore non-FOR UPDATE/SHARE items */
|
||||
continue; /* 忽略非for UPDATE/SHARE项 */
|
||||
}
|
||||
|
||||
if (RelationGetRelid(thiserm->relation) == table_oid) {
|
||||
|
|
@ -124,15 +118,14 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
|
||||
/*
|
||||
* The cursor must have a current result row: per the SQL spec, it's
|
||||
* an error if not.
|
||||
* 游标必须有当前结果行:根据SQL规范,如果没有,则会出现错误。
|
||||
*/
|
||||
if (portal->atStart || portal->atEnd) {
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE),
|
||||
errmsg("cursor \"%s\" is not positioned on a row when the cursor uses for UPDATE/SHARE", cursor_name)));
|
||||
}
|
||||
|
||||
/* Return the currently scanned TID, if there is one */
|
||||
/* 返回当前扫描的TID(如果有) */
|
||||
if (ItemPointerIsValid(&(erm->curCtid))) {
|
||||
*current_tid = erm->curCtid;
|
||||
|
||||
|
|
@ -144,9 +137,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
|
||||
/*
|
||||
* This table didn't produce the cursor's current row; some other
|
||||
* inheritance child of the same parent must have. Signal caller to
|
||||
* do nothing on this table.
|
||||
* 这个表没有产生游标的当前行;必须有同一父节点的其他继承子节点。信号调用者在表上什么都不做。
|
||||
*/
|
||||
return false;
|
||||
} else {
|
||||
|
|
@ -156,9 +147,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
ItemPointer tuple_tid;
|
||||
|
||||
/*
|
||||
* Without FOR UPDATE, we dig through the cursor's plan to find the
|
||||
* scan node. Fail if it's not there or buried underneath
|
||||
* aggregation.
|
||||
* 如果没有FOR UPDATE,我们将通过游标的计划来查找扫描节点。如果不存在或隐藏在聚合下面,则失败。
|
||||
*/
|
||||
scanstate = search_plan_tree(query_desc->planstate, table_oid);
|
||||
if (scanstate == NULL) {
|
||||
|
|
@ -168,23 +157,21 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
}
|
||||
|
||||
/*
|
||||
* The cursor must have a current result row: per the SQL spec, it's
|
||||
* an error if not. We test this at the top level, rather than at the
|
||||
* scan node level, because in inheritance cases any one table scan
|
||||
* could easily not be on a row. We want to return false, not raise
|
||||
* error, if the passed-in table OID is for one of the inactive scans.
|
||||
* 游标必须有当前结果行:根据SQL规范,如果没有,则会出现错误。
|
||||
* 我们在顶层测试,而不是在扫描节点级测试,因为在继承情况下,任何一个表扫描都很容易不在一行上。
|
||||
* 如果传入的表OID是用于非活动扫描的,我们希望返回false,而不是引发错误。
|
||||
*/
|
||||
if (portal->atStart || portal->atEnd) {
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg(
|
||||
"cursor \"%s\" is not positioned on a row when the cursor doesn't use for UPDATE/SHARE", cursor_name)));
|
||||
}
|
||||
|
||||
/* Now OK to return false if we found an inactive scan */
|
||||
/* 现在OK返回false如果我们发现一个非活动扫描 */
|
||||
if (TupIsNull(scanstate->ss_ScanTupleSlot)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Use slot_getattr to catch any possible mistakes */
|
||||
/* 使用slot_getattr捕获任何可能的错误 */
|
||||
tuple_tableoid = DatumGetObjectId(tableam_tslot_getattr(scanstate->ss_ScanTupleSlot, TableOidAttributeNumber, &lisnull));
|
||||
Assert(!lisnull);
|
||||
tuple_tid = (ItemPointer)DatumGetPointer(
|
||||
|
|
@ -206,7 +193,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio
|
|||
/*
|
||||
* fetch_cursor_param_value
|
||||
*
|
||||
* Fetch the string value of a param, verifying it is of type REFCURSOR.
|
||||
* 获取参数的字符串值,验证它是REFCURSOR类型。.
|
||||
*/
|
||||
static char *fetch_cursor_param_value(ExprContext *econtext, int paramId)
|
||||
{
|
||||
|
|
@ -215,20 +202,20 @@ static char *fetch_cursor_param_value(ExprContext *econtext, int paramId)
|
|||
if (paramInfo && paramId > 0 && paramId <= paramInfo->numParams) {
|
||||
ParamExternData *prm = ¶mInfo->params[paramId - 1];
|
||||
|
||||
/* give hook a chance in case parameter is dynamic */
|
||||
/* 如果参数是动态的,给钩子一个机会 */
|
||||
if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) {
|
||||
(*paramInfo->paramFetch)(paramInfo, paramId);
|
||||
}
|
||||
|
||||
if (OidIsValid(prm->ptype) && !prm->isnull) {
|
||||
/* safety check in case hook did something unexpected */
|
||||
/* 安全检查,以防钩子发生意外 */
|
||||
if (prm->ptype != REFCURSOROID) {
|
||||
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)", paramId,
|
||||
format_type_be(prm->ptype), format_type_be(REFCURSOROID))));
|
||||
}
|
||||
|
||||
/* We know that refcursor uses text's I/O routines */
|
||||
/* 我们知道refcursor使用text的I/O例程 */
|
||||
return TextDatumGetCString(prm->value);
|
||||
}
|
||||
}
|
||||
|
|
@ -240,8 +227,8 @@ static char *fetch_cursor_param_value(ExprContext *econtext, int paramId)
|
|||
/*
|
||||
* search_plan_tree
|
||||
*
|
||||
* Search through a PlanState tree for a scan node on the specified table.
|
||||
* Return NULL if not found or multiple candidates.
|
||||
* 在PlanState树中搜索指定表上的扫描节点。
|
||||
* 如果没有找到或有多个候选,则返回NULL。
|
||||
*/
|
||||
#ifdef PGXC
|
||||
ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
||||
|
|
@ -262,7 +249,7 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
}
|
||||
#endif
|
||||
/*
|
||||
* scan nodes can all be treated alike
|
||||
* 扫描节点都可以被同等对待
|
||||
*/
|
||||
case T_SeqScanState:
|
||||
case T_IndexScanState:
|
||||
|
|
@ -284,8 +271,7 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
return result;
|
||||
}
|
||||
/*
|
||||
* For Append, we must look through the members; watch out for
|
||||
* multiple matches (possible if it was from UNION ALL)
|
||||
* 对于Append,必须遍历成员;注意多个匹配(可能来自UNION ALL)
|
||||
*/
|
||||
case T_AppendState: {
|
||||
AppendState *astate = (AppendState *)node;
|
||||
|
|
@ -297,14 +283,14 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
if (elem == NULL)
|
||||
continue;
|
||||
if (result != NULL)
|
||||
return NULL; /* multiple matches */
|
||||
return NULL; /* 多个匹配 */
|
||||
|
||||
result = elem;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/*
|
||||
* Similarly for MergeAppend
|
||||
* 类似于MergeAppend
|
||||
*/
|
||||
case T_MergeAppendState: {
|
||||
MergeAppendState *mstate = (MergeAppendState *)node;
|
||||
|
|
@ -318,15 +304,14 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
continue;
|
||||
}
|
||||
if (result != NULL) {
|
||||
return NULL; /* multiple matches */
|
||||
return NULL; /* 多个匹配 */
|
||||
}
|
||||
result = elem;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/*
|
||||
* Result and Limit can be descended through (these are safe
|
||||
* because they always return their input's current row)
|
||||
* Result和Limit可以依次下降(它们是安全的,因为它们总是返回输入的当前行)
|
||||
*/
|
||||
#ifdef PGXC
|
||||
case T_MaterialState:
|
||||
|
|
@ -337,13 +322,13 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid)
|
|||
return search_plan_tree(node->lefttree, table_oid);
|
||||
|
||||
/*
|
||||
* SubqueryScan too, but it keeps the child in a different place
|
||||
* SubqueryScan也可以,但它将子对象保存在不同的位置
|
||||
*/
|
||||
case T_SubqueryScanState:
|
||||
return search_plan_tree(((SubqueryScanState *)node)->subplan, table_oid);
|
||||
|
||||
default:
|
||||
/* Otherwise, assume we can't descend through it */
|
||||
/* 否则,假设我们不能从里面下去 */
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
/* -------------------------------------------------------------------------
|
||||
*
|
||||
* execGrouping.cpp
|
||||
* executor utility routines for grouping, hashing, and aggregation
|
||||
* 用于分组、散列和聚合的执行程序实用程序例程
|
||||
*
|
||||
* Note: we currently assume that equality and hashing functions are not
|
||||
* collation-sensitive, so the code in this file has no support for passing
|
||||
* collation settings through from callers. That may have to change someday.
|
||||
* 注意:我们目前假设相等和散列函数对排序规则不敏感,因此此文件中的代码不支持从调用者传递排序规则设置。这种情况有一天可能会改变。
|
||||
*
|
||||
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
* 华为技术有限公司版权所有
|
||||
* 部分版权所有(c) 1996-2012, PostgreSQL全球发展集团
|
||||
* 版权所有(c) 1994,加州大学董事会
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
|
|
@ -31,22 +29,21 @@ static uint32 TupleHashTableHash(const void* key, Size keysize);
|
|||
static int TupleHashTableMatch(const void* key1, const void* key2, Size keysize);
|
||||
|
||||
/*****************************************************************************
|
||||
* Utility routines for grouping tuples together
|
||||
* 将元组分组在一起的实用程序例程
|
||||
*****************************************************************************/
|
||||
/*
|
||||
* execTuplesMatch
|
||||
* Return true if two tuples match in all the indicated fields.
|
||||
* 如果两个元组在所有指定字段中匹配,则返回true。
|
||||
*
|
||||
* This actually implements SQL's notion of "not distinct". Two nulls
|
||||
* match, a null and a not-null don't match.
|
||||
* 这实际上实现了SQL的“不区分”概念。两个空匹配,一个空和一个非空不匹配。
|
||||
*
|
||||
* slot1, slot2: the tuples to compare (must have same columns!)
|
||||
* numCols: the number of attributes to be examined
|
||||
* matchColIdx: array of attribute column numbers
|
||||
* eqFunctions: array of fmgr lookup info for the equality functions to use
|
||||
* evalContext: short-term memory context for executing the functions
|
||||
* slot1, slot2:要比较的元组(必须有相同的列!)
|
||||
* numCols:要检查的属性数量
|
||||
* matchColIdx:属性列号的数组
|
||||
* eqFunctions:为相等函数使用的fmgr查找信息的数组
|
||||
* evalContext:用于执行函数的短期内存上下文
|
||||
*
|
||||
* NB: evalContext is reset each time!
|
||||
* NB: 每次都重置evalContext !
|
||||
*/
|
||||
bool execTuplesMatch(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols, AttrNumber* matchColIdx,
|
||||
FmgrInfo* eqfunctions, MemoryContext evalContext)
|
||||
|
|
@ -55,15 +52,15 @@ bool execTuplesMatch(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols,
|
|||
bool result = false;
|
||||
int i;
|
||||
|
||||
/* Reset and switch into the temp context. */
|
||||
/* 重置并切换到temp上下文。 */
|
||||
MemoryContextReset(evalContext);
|
||||
oldContext = MemoryContextSwitchTo(evalContext);
|
||||
|
||||
/*
|
||||
* We cannot report a match without checking all the fields, but we can
|
||||
* report a non-match as soon as we find unequal fields. So, start
|
||||
* comparing at the last field (least significant sort key). That's the
|
||||
* most likely to be different if we are dealing with sorted input.
|
||||
* 如果不检查所有字段,我们就不能报告匹配,
|
||||
* 但是一旦发现不相等的字段,我们就可以报告不匹配。
|
||||
* 因此,从最后一个字段(最不重要的排序键)开始比较。
|
||||
* 这是最有可能不同的如果我们处理的是排序输入。
|
||||
*/
|
||||
result = true;
|
||||
|
||||
|
|
@ -78,17 +75,17 @@ bool execTuplesMatch(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols,
|
|||
attr2 = tableam_tslot_getattr(slot2, att, &isNull2);
|
||||
|
||||
if (isNull1 != isNull2) {
|
||||
result = false; /* one null and one not; they aren't equal */
|
||||
result = false; /* 一个null,一个not;它们是不相等的 */
|
||||
break;
|
||||
}
|
||||
|
||||
if (isNull1) {
|
||||
continue; /* both are null, treat as equal */
|
||||
continue; /* 两者都为空,同等对待 */
|
||||
}
|
||||
|
||||
/* Apply the type-specific equality function */
|
||||
/* 应用特定于类型的相等函数 */
|
||||
if (!DatumGetBool(FunctionCall2(&eqfunctions[i], attr1, attr2))) {
|
||||
result = false; /* they aren't equal */
|
||||
result = false; /* 它们是不相等的 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -100,13 +97,11 @@ bool execTuplesMatch(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols,
|
|||
|
||||
/*
|
||||
* execTuplesUnequal
|
||||
* Return true if two tuples are definitely unequal in the indicated
|
||||
* fields.
|
||||
* 如果两个元组在指定字段中绝对不相等,则返回true。
|
||||
*
|
||||
* Nulls are neither equal nor unequal to anything else. A true result
|
||||
* is obtained only if there are non-null fields that compare not-equal.
|
||||
* null既不等于也不等于其他任何东西。只有当存在比较not-equal的非空字段时,才能获得真结果。
|
||||
*
|
||||
* Parameters are identical to execTuplesMatch.
|
||||
* 参数与execTuplesMatch相同。
|
||||
*/
|
||||
bool execTuplesUnequal(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols, AttrNumber* matchColIdx,
|
||||
FmgrInfo* eqfunctions, MemoryContext evalContext)
|
||||
|
|
@ -117,15 +112,14 @@ bool execTuplesUnequal(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols
|
|||
|
||||
Assert(slot1->tts_tupleDescriptor->tdTableAmType == slot2->tts_tupleDescriptor->tdTableAmType);
|
||||
|
||||
/* Reset and switch into the temp context. */
|
||||
/* 重置并切换到temp上下文 */
|
||||
MemoryContextReset(evalContext);
|
||||
oldContext = MemoryContextSwitchTo(evalContext);
|
||||
|
||||
/*
|
||||
* We cannot report a match without checking all the fields, but we can
|
||||
* report a non-match as soon as we find unequal fields. So, start
|
||||
* comparing at the last field (least significant sort key). That's the
|
||||
* most likely to be different if we are dealing with sorted input.
|
||||
* 如果不检查所有字段,我们就不能报告匹配,但是一旦发现不相等的字段,我们就可以报告不匹配。
|
||||
* 因此,从最后一个字段(最不重要的排序键)开始比较。
|
||||
* 这是最有可能不同的如果我们处理的是排序输入。
|
||||
*/
|
||||
result = false;
|
||||
|
||||
|
|
@ -138,18 +132,18 @@ bool execTuplesUnequal(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols
|
|||
attr1 = tableam_tslot_getattr(slot1, att, &isNull1);
|
||||
|
||||
if (isNull1) {
|
||||
continue; /* can't prove anything here */
|
||||
continue; /* 不能证明什么 */
|
||||
}
|
||||
|
||||
attr2 = tableam_tslot_getattr(slot2, att, &isNull2);
|
||||
|
||||
if (isNull2) {
|
||||
continue; /* can't prove anything here */
|
||||
continue; /* 不能证明什么 */
|
||||
}
|
||||
|
||||
/* Apply the type-specific equality function */
|
||||
/* 应用特定于类型的相等函数 */
|
||||
if (!DatumGetBool(FunctionCall2(&eqfunctions[i], attr1, attr2))) {
|
||||
result = true; /* they are unequal */
|
||||
result = true; /* 它们是不相等的 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -161,10 +155,9 @@ bool execTuplesUnequal(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols
|
|||
|
||||
/*
|
||||
* execTuplesMatchPrepare
|
||||
* Look up the equality functions needed for execTuplesMatch or
|
||||
* execTuplesUnequal, given an array of equality operator OIDs.
|
||||
* 给定一个相等操作符oid数组,查找execTuplesMatch或exectuplesinequality所需的相等函数。
|
||||
*
|
||||
* The result is a palloc'd array.
|
||||
* 结果是一个lolod数组。
|
||||
*/
|
||||
FmgrInfo* execTuplesMatchPrepare(int numCols, Oid* eqOperators)
|
||||
{
|
||||
|
|
@ -184,13 +177,12 @@ FmgrInfo* execTuplesMatchPrepare(int numCols, Oid* eqOperators)
|
|||
|
||||
/*
|
||||
* execTuplesHashPrepare
|
||||
* Look up the equality and hashing functions needed for a TupleHashTable.
|
||||
* 查找TupleHashTable所需的相等和散列函数。
|
||||
*
|
||||
* This is similar to execTuplesMatchPrepare, but we also need to find the
|
||||
* hash functions associated with the equality operators. *eqFunctions and
|
||||
* *hashFunctions receive the palloc'd result arrays.
|
||||
* 这类似于execTuplesMatchPrepare,但我们还需要找到与相等操作符相关的散列函数。
|
||||
* *eqFunctions和*hashFunctions接收palloc结果数组。
|
||||
*
|
||||
* Note: we expect that the given operators are not cross-type comparisons.
|
||||
* 注意:我们期望给定的操作符不是跨类型比较。
|
||||
*/
|
||||
void execTuplesHashPrepare(int numCols, Oid* eqOperators, FmgrInfo** eqFunctions, FmgrInfo** hashFunctions)
|
||||
{
|
||||
|
|
@ -216,7 +208,7 @@ void execTuplesHashPrepare(int numCols, Oid* eqOperators, FmgrInfo** eqFunctions
|
|||
i,
|
||||
numCols)));
|
||||
|
||||
/* We're not supporting cross-type cases here */
|
||||
/* 我们不支持交叉类型的情况 */
|
||||
Assert(left_hash_function == right_hash_function);
|
||||
fmgr_info(eq_function, &(*eqFunctions)[i]);
|
||||
fmgr_info(right_hash_function, &(*hashFunctions)[i]);
|
||||
|
|
@ -224,29 +216,23 @@ void execTuplesHashPrepare(int numCols, Oid* eqOperators, FmgrInfo** eqFunctions
|
|||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Utility routines for all-in-memory hash tables
|
||||
* 全内存哈希表的实用程序例程
|
||||
*
|
||||
* These routines build hash tables for grouping tuples together (eg, for
|
||||
* hash aggregation). There is one entry for each not-distinct set of tuples
|
||||
* presented.
|
||||
* 这些例程构建哈希表,将元组分组在一起(例如,用于哈希聚合)。
|
||||
* 对于所呈现的每个不明显的元组集合,都有一个条目。
|
||||
*****************************************************************************/
|
||||
/*
|
||||
* Construct an empty TupleHashTable
|
||||
* 构造一个空的TupleHashTable
|
||||
*
|
||||
* numCols, keyColIdx: identify the tuple fields to use as lookup key
|
||||
* eqfunctions: equality comparison functions to use
|
||||
* hashfunctions: datatype-specific hashing functions to use
|
||||
* nbuckets: initial estimate of hashtable size
|
||||
* entrysize: size of each entry (at least sizeof(TupleHashEntryData))
|
||||
* tablecxt: memory context in which to store table and table entries
|
||||
* tempcxt: short-lived context for evaluation hash and comparison functions
|
||||
* numCols, keyColIdx:确定元组字段作为查找键使用eqfunctions:
|
||||
* 相等比较函数使用hashfunctions:特定于数据类型的哈希函数使用nbuckets:
|
||||
* 哈希表大小的初始估计entrysize:每个表项的大小(至少sizeof(TupleHashEntryData))
|
||||
* tablext:存储表和表项的内存上下文tempcxt:评估哈希和比较函数的短期上下文
|
||||
*
|
||||
* The function arrays may be made with execTuplesHashPrepare(). Note they
|
||||
* are not cross-type functions, but expect to see the table datatype(s)
|
||||
* on both sides.
|
||||
* 函数数组可以用execTuplesHashPrepare()创建。
|
||||
* 注意,它们不是跨类型函数,但期望在两边看到表数据类型。
|
||||
*
|
||||
* Note that keyColIdx, eqfunctions, and hashfunctions must be allocated in
|
||||
* storage that will live as long as the hashtable does.
|
||||
* 请注意,keyColIdx、eqfunctions和hashfunctions必须分配到与散列表存在时间一样长的存储中。
|
||||
*/
|
||||
TupleHashTable BuildTupleHashTable(int numCols, AttrNumber* keyColIdx, FmgrInfo* eqfunctions, FmgrInfo* hashfunctions,
|
||||
long nbuckets, Size entrysize, MemoryContext tablecxt, MemoryContext tempcxt, int workMem)
|
||||
|
|
@ -257,7 +243,7 @@ TupleHashTable BuildTupleHashTable(int numCols, AttrNumber* keyColIdx, FmgrInfo*
|
|||
Assert(nbuckets > 0);
|
||||
Assert(entrysize >= sizeof(TupleHashEntryData));
|
||||
|
||||
/* Limit initial table size request to not more than work_mem */
|
||||
/* 限制初始表大小请求不超过work_mem */
|
||||
nbuckets = Min(nbuckets, (long)((workMem * 1024L) / entrysize));
|
||||
if (u_sess->attr.attr_sql.hashagg_table_size != 0)
|
||||
nbuckets = Min(nbuckets, u_sess->attr.attr_sql.hashagg_table_size);
|
||||
|
|
@ -271,7 +257,7 @@ TupleHashTable BuildTupleHashTable(int numCols, AttrNumber* keyColIdx, FmgrInfo*
|
|||
hashtable->tablecxt = tablecxt;
|
||||
hashtable->tempcxt = tempcxt;
|
||||
hashtable->entrysize = entrysize;
|
||||
hashtable->tableslot = NULL; /* will be made on first lookup */
|
||||
hashtable->tableslot = NULL; /* 将在第一次查找时进行 */
|
||||
hashtable->inputslot = NULL;
|
||||
hashtable->in_hash_funcs = NULL;
|
||||
hashtable->cur_eq_funcs = NULL;
|
||||
|
|
@ -293,20 +279,16 @@ TupleHashTable BuildTupleHashTable(int numCols, AttrNumber* keyColIdx, FmgrInfo*
|
|||
}
|
||||
|
||||
/*
|
||||
* Find or create a hashtable entry for the tuple group containing the
|
||||
* given tuple. The tuple must be the same type as the hashtable entries.
|
||||
* 查找或创建包含给定元组的元组的哈希表项。元组必须与哈希表项的类型相同。
|
||||
*
|
||||
* If isnew is NULL, we do not create new entries; we return NULL if no
|
||||
* match is found.
|
||||
* 如果isnew为NULL,则不创建新表项;如果没有找到匹配,则返回NULL。
|
||||
*
|
||||
* If isnew isn't NULL, then a new entry is created if no existing entry
|
||||
* matches. On return, *isnew is true if the entry is newly created,
|
||||
* false if it existed already. Any extra space in a new entry has been
|
||||
* zeroed.
|
||||
* 如果isnew不为NULL,则在没有现有条目匹配的情况下创建新条目。
|
||||
* 返回时,如果条目是新创建的,*isnew为true,
|
||||
* 如果条目已经存在,则为false。新条目中的任何额外空间都被归零。
|
||||
*
|
||||
* If isinserthashtbl is false, the para of hash search is HASH_FIND
|
||||
* instead of HASH_ENTER. This slot will be insert into temp file instead of
|
||||
* hash table if it is new
|
||||
* 如果isinserthashtbl为false,则哈希搜索的参数为HASH_FIND,而不是HASH_ENTER。
|
||||
* 如果这个槽是新的,它将被插入到临时文件中,而不是哈希表中
|
||||
*
|
||||
*/
|
||||
TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* slot, bool* isnew, bool isinserthashtbl)
|
||||
|
|
@ -317,29 +299,27 @@ TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* sl
|
|||
TupleHashEntryData dummy;
|
||||
bool found = false;
|
||||
|
||||
/* If first time through, clone the input slot to make table slot */
|
||||
/* 如果第一次通过,克隆输入槽来制作表槽 */
|
||||
if (hashtable->tableslot == NULL) {
|
||||
TupleDesc tupdesc;
|
||||
|
||||
oldContext = MemoryContextSwitchTo(hashtable->tablecxt);
|
||||
|
||||
/*
|
||||
* We copy the input tuple descriptor just for safety --- we assume
|
||||
* all input tuples will have equivalent descriptors.
|
||||
* 为了安全起见,我们复制了输入元组描述符——我们假设所有输入元组都具有相同的描述符。
|
||||
*/
|
||||
tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
|
||||
hashtable->tableslot = MakeSingleTupleTableSlot(tupdesc);
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
/* Need to run the hash functions in short-lived context */
|
||||
/* 需要在短期上下文中运行哈希函数 */
|
||||
oldContext = MemoryContextSwitchTo(hashtable->tempcxt);
|
||||
|
||||
/*
|
||||
* Set up data needed by hash and match functions
|
||||
* 设置哈希和匹配函数所需的数据
|
||||
*
|
||||
* We save and restore u_sess->exec_cxt.cur_tuple_hash_table just in case someone manages to
|
||||
* invoke this code re-entrantly.
|
||||
* 我们保存并恢复u_sess-> exec_ext。Cur_tuple_hash_table,以防有人设法重新调用这段代码。
|
||||
*/
|
||||
hashtable->inputslot = slot;
|
||||
hashtable->in_hash_funcs = hashtable->tab_hash_funcs;
|
||||
|
|
@ -348,34 +328,33 @@ TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* sl
|
|||
saveCurHT = u_sess->exec_cxt.cur_tuple_hash_table;
|
||||
u_sess->exec_cxt.cur_tuple_hash_table = hashtable;
|
||||
|
||||
/* Search the hash table */
|
||||
dummy.firstTuple = NULL; /* flag to reference inputslot */
|
||||
/* 搜索哈希表 */
|
||||
dummy.firstTuple = NULL; /* 引用输入槽的标志 */
|
||||
|
||||
if (isinserthashtbl) {
|
||||
entry = (TupleHashEntry)hash_search(hashtable->hashtab, &dummy, isnew ? HASH_ENTER : HASH_FIND, &found);
|
||||
} else {
|
||||
/* this slot will be insert into temp file instead of hash table if it is not found in hash table */
|
||||
/* 如果在哈希表中没有找到该槽位,则将其插入临时文件而不是哈希表中 */
|
||||
entry = (TupleHashEntry)hash_search(hashtable->hashtab, &dummy, HASH_FIND, &found);
|
||||
}
|
||||
|
||||
if (isnew != NULL) {
|
||||
if (found) {
|
||||
/* found pre-existing entry */
|
||||
/* 发现已有条目 */
|
||||
*isnew = false;
|
||||
} else {
|
||||
if (entry) {
|
||||
Assert(isinserthashtbl);
|
||||
/*
|
||||
* created new entry
|
||||
* 创建新条目
|
||||
*
|
||||
* Zero any caller-requested space in the entry. (This zaps the
|
||||
* "key data" dynahash.c copied into the new entry, but we don't
|
||||
* care since we're about to overwrite it anyway.)
|
||||
* 条目中所有调用者请求的空间为零。
|
||||
* (这会将“关键数据”dynahash.c复制到新条目中,但我们并不关心,因为我们无论如何都要覆盖它。)
|
||||
*/
|
||||
errno_t errorno = memset_s(entry, hashtable->entrysize, 0, hashtable->entrysize);
|
||||
securec_check(errorno, "\0", "\0");
|
||||
|
||||
/* Copy the first tuple into the table context */
|
||||
/* 将第一个元组复制到表上下文中 */
|
||||
MemoryContextSwitchTo(hashtable->tablecxt);
|
||||
entry->firstTuple = ExecCopySlotMinimalTuple(slot);
|
||||
if (hashtable->add_width)
|
||||
|
|
@ -394,13 +373,9 @@ TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* sl
|
|||
}
|
||||
|
||||
/*
|
||||
* Search for a hashtable entry matching the given tuple. No entry is
|
||||
* created if there's not a match. This is similar to the non-creating
|
||||
* case of LookupTupleHashEntry, except that it supports cross-type
|
||||
* comparisons, in which the given tuple is not of the same type as the
|
||||
* table entries. The caller must provide the hash functions to use for
|
||||
* the input tuple, as well as the equality functions, since these may be
|
||||
* different from the table's internal functions.
|
||||
* 搜索与给定元组匹配的散列表项。如果不匹配,则不创建条目。
|
||||
* 这类似于LookupTupleHashEntry的非创建情况,只是它支持跨类型比较,在这种比较中,给定的元组与表项的类型不同。
|
||||
* 调用者必须提供用于输入元组的散列函数以及相等函数,因为这些函数可能不同于表的内部函数。
|
||||
*/
|
||||
TupleHashEntry FindTupleHashEntry(
|
||||
TupleHashTable hashtable, TupleTableSlot* slot, FmgrInfo* eqfunctions, FmgrInfo* hashfunctions)
|
||||
|
|
@ -410,14 +385,13 @@ TupleHashEntry FindTupleHashEntry(
|
|||
TupleHashTable saveCurHT;
|
||||
TupleHashEntryData dummy;
|
||||
|
||||
/* Need to run the hash functions in short-lived context */
|
||||
/* 需要在短期上下文中运行哈希函数 */
|
||||
oldContext = MemoryContextSwitchTo(hashtable->tempcxt);
|
||||
|
||||
/*
|
||||
* Set up data needed by hash and match functions
|
||||
* 设置哈希和匹配函数所需的数据
|
||||
*
|
||||
* We save and restore u_sess->exec_cxt.cur_tuple_hash_table just in case someone manages to
|
||||
* invoke this code re-entrantly.
|
||||
* 我们保存并恢复u_sess-> exec_ext。Cur_tuple_hash_table,以防有人设法重新调用这段代码。
|
||||
*/
|
||||
hashtable->inputslot = slot;
|
||||
hashtable->in_hash_funcs = hashfunctions;
|
||||
|
|
@ -426,8 +400,8 @@ TupleHashEntry FindTupleHashEntry(
|
|||
saveCurHT = u_sess->exec_cxt.cur_tuple_hash_table;
|
||||
u_sess->exec_cxt.cur_tuple_hash_table = hashtable;
|
||||
|
||||
/* Search the hash table */
|
||||
dummy.firstTuple = NULL; /* flag to reference inputslot */
|
||||
/* 搜索哈希表 */
|
||||
dummy.firstTuple = NULL; /* 引用输入槽的标志 */
|
||||
entry = (TupleHashEntry)hash_search(hashtable->hashtab, &dummy, HASH_FIND, NULL);
|
||||
|
||||
u_sess->exec_cxt.cur_tuple_hash_table = saveCurHT;
|
||||
|
|
@ -438,20 +412,19 @@ TupleHashEntry FindTupleHashEntry(
|
|||
}
|
||||
|
||||
/*
|
||||
* Compute the hash value for a tuple
|
||||
* 计算元组的哈希值
|
||||
*
|
||||
* The passed-in key is a pointer to TupleHashEntryData. In an actual hash
|
||||
* table entry, the firstTuple field points to a tuple (in MinimalTuple
|
||||
* format). LookupTupleHashEntry sets up a dummy TupleHashEntryData with a
|
||||
* NULL firstTuple field --- that cues us to look at the inputslot instead.
|
||||
* This convention avoids the need to materialize virtual input tuples unless
|
||||
* they actually need to get copied into the table.
|
||||
* 传入的键是一个指向TupleHashEntryData的指针。
|
||||
* 在实际的哈希表条目中,第一个tuple字段指向一个元组(在MinimalTuple格式中)。
|
||||
* LookupTupleHashEntry用一个NULL firstTuple字段——这提示我们查看输入槽。
|
||||
* 这种约定避免了具体化虚拟输入元组的需要,
|
||||
* 除非它们实际上需要被复制到表中。
|
||||
*
|
||||
* u_sess->exec_cxt.cur_tuple_hash_table must be set before calling this, since dynahash.c
|
||||
* doesn't provide any API that would let us get at the hashtable otherwise.
|
||||
* u_sess - > exec_cxt。cur_tuple_hash_table必须在调用它之前设置,
|
||||
* 因为dynahash.c没有提供任何让我们以其他方式获取哈希表的API。
|
||||
*
|
||||
* Also, the caller must select an appropriate memory context for running
|
||||
* the hash functions. (dynahash.c doesn't change CurrentMemoryContext.)
|
||||
* 此外,调用者必须为运行散列函数选择适当的内存上下文。
|
||||
* (dynahash.c不会改变CurrentMemoryContext。)
|
||||
*/
|
||||
static uint32 TupleHashTableHash(const void* key, Size keysize)
|
||||
{
|
||||
|
|
@ -465,28 +438,28 @@ static uint32 TupleHashTableHash(const void* key, Size keysize)
|
|||
int i;
|
||||
|
||||
if (tuple == NULL) {
|
||||
/* Process the current input tuple for the table */
|
||||
/* 处理表的当前输入元组 */
|
||||
slot = hashtable->inputslot;
|
||||
hashfunctions = hashtable->in_hash_funcs;
|
||||
} else {
|
||||
/* Process a tuple already stored in the table */
|
||||
/* (this case never actually occurs in current dynahash.c code) */
|
||||
/* 处理已经存储在表中的元组 */
|
||||
/* (这种情况在当前的dynahash.c代码中从未发生过) */
|
||||
slot = hashtable->tableslot;
|
||||
ExecStoreMinimalTuple(tuple, slot, false);
|
||||
hashfunctions = hashtable->tab_hash_funcs;
|
||||
}
|
||||
|
||||
/* Get the Table Accessor Method*/
|
||||
/* 获取表访问器方法*/
|
||||
for (i = 0; i < numCols; i++) {
|
||||
AttrNumber att = keyColIdx[i];
|
||||
Datum attr;
|
||||
bool isNull = false;
|
||||
|
||||
/* rotate hashkey left 1 bit at each step */
|
||||
/* 每一步将哈希键向左旋转1位 */
|
||||
hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
|
||||
|
||||
attr = tableam_tslot_getattr(slot, att, &isNull);
|
||||
/* treat nulls as having hash key 0 */
|
||||
/* 将空值视为哈希键为0 */
|
||||
if (!isNull) {
|
||||
uint32 hkey;
|
||||
hkey = DatumGetUInt32(FunctionCall1(&hashfunctions[i], attr));
|
||||
|
|
@ -500,15 +473,13 @@ static uint32 TupleHashTableHash(const void* key, Size keysize)
|
|||
}
|
||||
|
||||
/*
|
||||
* See whether two tuples (presumably of the same hash value) match
|
||||
* 查看两个元组(假设具有相同的哈希值)是否匹配
|
||||
*
|
||||
* As above, the passed pointers are pointers to TupleHashEntryData.
|
||||
* 如上所述,传递的指针是指向TupleHashEntryData的指针。
|
||||
*
|
||||
* u_sess->exec_cxt.cur_tuple_hash_table must be set before calling this, since dynahash.c
|
||||
* doesn't provide any API that would let us get at the hashtable otherwise.
|
||||
* u_sess - > exec_cxt。cur_tuple_hash_table必须在调用它之前设置,因为dynahash.c没有提供任何让我们以其他方式获取哈希表的API
|
||||
*
|
||||
* Also, the caller must select an appropriate memory context for running
|
||||
* the compare functions. (dynahash.c doesn't change CurrentMemoryContext.)
|
||||
* 此外,调用者必须为运行比较函数选择适当的内存上下文。(dynahash.c不会改变CurrentMemoryContext。)
|
||||
*/
|
||||
static int TupleHashTableMatch(const void* key1, const void* key2, Size keysize)
|
||||
{
|
||||
|
|
@ -522,10 +493,9 @@ static int TupleHashTableMatch(const void* key1, const void* key2, Size keysize)
|
|||
TupleHashTable hashtable = u_sess->exec_cxt.cur_tuple_hash_table;
|
||||
|
||||
/*
|
||||
* We assume that dynahash.c will only ever call us with the first
|
||||
* argument being an actual table entry, and the second argument being
|
||||
* LookupTupleHashEntry's dummy TupleHashEntryData. The other direction
|
||||
* could be supported too, but is not currently used by dynahash.c.
|
||||
* 我们假设dynahash.c调用我们时,第一个参数是一个实际的表条目,
|
||||
* 第二个参数是LookupTupleHashEntry的假TupleHashEntryData。
|
||||
* 另一个方向也可以支持,但目前没有被dynahash.c使用。
|
||||
*/
|
||||
Assert(tuple1 != NULL);
|
||||
slot1 = hashtable->tableslot;
|
||||
|
|
@ -533,7 +503,7 @@ static int TupleHashTableMatch(const void* key1, const void* key2, Size keysize)
|
|||
Assert(tuple2 == NULL);
|
||||
slot2 = hashtable->inputslot;
|
||||
|
||||
/* For crosstype comparisons, the inputslot must be first */
|
||||
/* 对于交叉类型比较,输入槽必须是第一个 */
|
||||
if (execTuplesMatch(
|
||||
slot2, slot1, hashtable->numCols, hashtable->keyColIdx, hashtable->cur_eq_funcs, hashtable->tempcxt))
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
/* -------------------------------------------------------------------------
|
||||
* execJunk.cpp
|
||||
* 垃圾属性支持相关内容...
|
||||
*
|
||||
* execJunk.cpp
|
||||
* Junk attribute support stuff....
|
||||
* 版权部分 (c) 2020 华为技术有限公司
|
||||
* 版权部分 (c) 1996-2012,PostgreSQL全球开发团队
|
||||
* 版权部分 (c) 1994,加利福尼亚大学董事会
|
||||
*
|
||||
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* 标识
|
||||
* src/gausskernel/runtime/executor/execJunk.cpp
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
|
|
@ -21,84 +19,54 @@
|
|||
#include "pgxc/pgxc.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* XXX this stuff should be rewritten to take advantage
|
||||
* of ExecProject() and the ProjectionInfo node.
|
||||
* -cim 6/3/91
|
||||
*
|
||||
* An attribute of a tuple living inside the executor, can be
|
||||
* either a normal attribute or a "junk" attribute. "junk" attributes
|
||||
* never make it out of the executor, i.e. they are never printed,
|
||||
* returned or stored on disk. Their only purpose in life is to
|
||||
* store some information useful only to the executor, mainly the values
|
||||
* of system attributes like "ctid", or sort key columns that are not to
|
||||
* be output.
|
||||
*
|
||||
* The general idea is the following: A target list consists of a list of
|
||||
* TargetEntry nodes containing expressions. Each TargetEntry has a field
|
||||
* called 'resjunk'. If the value of this field is true then the
|
||||
* corresponding attribute is a "junk" attribute.
|
||||
*
|
||||
* When we initialize a plan we call ExecInitJunkFilter to create a filter.
|
||||
*
|
||||
* We then execute the plan, treating the resjunk attributes like any others.
|
||||
*
|
||||
* Finally, when at the top level we get back a tuple, we can call
|
||||
* ExecFindJunkAttribute/ExecGetJunkAttribute to retrieve the values of the
|
||||
* junk attributes we are interested in, and ExecFilterJunk to remove all the
|
||||
* junk attributes from a tuple. This new "clean" tuple is then printed,
|
||||
* inserted, or updated.
|
||||
* XXX 这部分应该被重新编写以利用 ExecProject() 和 ProjectionInfo 节点。
|
||||
* -cim 6/3/91
|
||||
|
||||
* 在执行器内部的元组的属性可以是普通属性,也可以是 "垃圾" 属性。"垃圾" 属性永远不会离开执行器,即它们永远不会被打印、返回或存储在磁盘上。它们的唯一目的是存储一些仅对执行器有用的信息,
|
||||
* 主要是系统属性如 "ctid" 的值,或者不会被输出的排序键列。
|
||||
* 总体思想如下:目标列表由包含表达式的 TargetEntry 节点列表组成。
|
||||
* 每个 TargetEntry 都有一个名为 'resjunk' 的字段。如果该字段的值为 true,则相应的属性是 "垃圾" 属性。
|
||||
* 当我们初始化一个计划时,我们调用 ExecInitJunkFilter 来创建一个过滤器。
|
||||
* 然后,我们执行计划,将 resjunk 属性视为其他属性一样处理。
|
||||
* 最后,当我们在顶层得到一个元组时,我们可以调用 ExecFindJunkAttribute/ExecGetJunkAttribute来检索我们感兴趣的垃圾属性的值,以及调用 ExecFilterJunk 来从元组中删除所有垃圾属性。
|
||||
* 最终,这个新的 "干净" 元组被打印、插入或更新。
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
/*
|
||||
* ExecInitJunkFilter
|
||||
*
|
||||
* Initialize the Junk filter.
|
||||
*
|
||||
* The source targetlist is passed in. The output tuple descriptor is
|
||||
* built from the non-junk tlist entries, plus the passed specification
|
||||
* of whether to include room for an OID or not.
|
||||
* An optional resultSlot can be passed as well.
|
||||
|
||||
* 初始化垃圾过滤器。根据目标列表、是否包含 OID、结果插槽的元组类型,在垃圾过滤器中创建并初始化相关数据结构。
|
||||
* 源目标列表被传入,输出元组描述符是从非垃圾 tlist 条目构建的,
|
||||
* 再加上传入的是否包括 OID 的规范。
|
||||
* 也可以传入一个可选的 resultSlot。
|
||||
*/
|
||||
JunkFilter* ExecInitJunkFilter(List* targetList, bool hasoid, TupleTableSlot* slot, TableAmType tam)
|
||||
{
|
||||
JunkFilter* junkfilter = NULL;
|
||||
TupleDesc cleanTupType;
|
||||
int cleanLength;
|
||||
AttrNumber* cleanMap = NULL;
|
||||
ListCell* t = NULL;
|
||||
AttrNumber cleanResno;
|
||||
JunkFilter* junkfilter = NULL;
|
||||
TupleDesc cleanTupType;
|
||||
int cleanLength;
|
||||
AttrNumber* cleanMap = NULL;
|
||||
ListCell* t = NULL;
|
||||
AttrNumber cleanResno;
|
||||
|
||||
/*
|
||||
* Compute the tuple descriptor for the cleaned tuple.
|
||||
*/
|
||||
cleanTupType = ExecCleanTypeFromTL(targetList, hasoid, tam);
|
||||
// 计算清理后的元组描述符
|
||||
cleanTupType = ExecCleanTypeFromTL(targetList, hasoid, tam);
|
||||
|
||||
/*
|
||||
* Use the given slot, or make a new slot if we weren't given one.
|
||||
*/
|
||||
// 设置槽的描述符,如果给定了槽,则使用给定的槽,否则创建一个新槽
|
||||
if (slot != NULL)
|
||||
ExecSetSlotDescriptor(slot, cleanTupType);
|
||||
else
|
||||
slot = MakeSingleTupleTableSlot(cleanTupType);
|
||||
|
||||
/*
|
||||
* Now calculate the mapping between the original tuple's attributes and
|
||||
* the "clean" tuple's attributes.
|
||||
*
|
||||
* The "map" is an array of "cleanLength" attribute numbers, i.e. one
|
||||
* entry for every attribute of the "clean" tuple. The value of this entry
|
||||
* is the attribute number of the corresponding attribute of the
|
||||
* "original" tuple. (Zero indicates a NULL output attribute, but we do
|
||||
* not use that feature in this routine.)
|
||||
*/
|
||||
cleanLength = cleanTupType->natts;
|
||||
cleanLength = cleanTupType->natts;
|
||||
if (cleanLength > 0) {
|
||||
cleanMap = (AttrNumber*)palloc(cleanLength * sizeof(AttrNumber));
|
||||
cleanMap = (AttrNumber*)palloc(cleanLength * sizeof(AttrNumber));
|
||||
cleanResno = 1;
|
||||
foreach (t, targetList) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(t);
|
||||
|
||||
|
||||
// 如果不是 "junk" 属性,则建立属性映射关系
|
||||
if (!tle->resjunk) {
|
||||
cleanMap[cleanResno - 1] = tle->resno;
|
||||
cleanResno++;
|
||||
|
|
@ -108,11 +76,10 @@ JunkFilter* ExecInitJunkFilter(List* targetList, bool hasoid, TupleTableSlot* sl
|
|||
cleanMap = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finally create and initialize the JunkFilter struct.
|
||||
*/
|
||||
// 创建并初始化 JunkFilter 结构
|
||||
junkfilter = makeNode(JunkFilter);
|
||||
|
||||
// 填充 JunkFilter 结构的字段
|
||||
junkfilter->jf_targetList = targetList;
|
||||
junkfilter->jf_cleanTupType = cleanTupType;
|
||||
junkfilter->jf_cleanMap = cleanMap;
|
||||
|
|
@ -124,78 +91,64 @@ JunkFilter* ExecInitJunkFilter(List* targetList, bool hasoid, TupleTableSlot* sl
|
|||
/*
|
||||
* ExecInitJunkFilterConversion
|
||||
*
|
||||
* Initialize a JunkFilter for rowtype conversions.
|
||||
*
|
||||
* Here, we are given the target "clean" tuple descriptor rather than
|
||||
* inferring it from the targetlist. The target descriptor can contain
|
||||
* deleted columns. It is assumed that the caller has checked that the
|
||||
* non-deleted columns match up with the non-junk columns of the targetlist.
|
||||
* 为行类型转换初始化垃圾过滤器。根据目标列表、干净元组类型、结果插槽的元组类型,在垃圾过滤器中创建并初始化相关数据结构。
|
||||
* 在这里,我们提供了目标“干净”元组描述符,而不是从目标列表中推断出来的。
|
||||
* 目标描述符可以包含已删除的列。假设调用者已经检查过非删除的列与目标列表的非垃圾列相匹配。
|
||||
*/
|
||||
JunkFilter* ExecInitJunkFilterConversion(List* targetList, TupleDesc cleanTupType, TupleTableSlot* slot)
|
||||
{
|
||||
JunkFilter* junkfilter = NULL;
|
||||
int cleanLength;
|
||||
AttrNumber* cleanMap = NULL;
|
||||
ListCell* t = NULL;
|
||||
int i;
|
||||
JunkFilter* junkfilter = NULL;
|
||||
int cleanLength;
|
||||
AttrNumber* cleanMap = NULL;
|
||||
ListCell* t = NULL;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Use the given slot, or make a new slot if we weren't given one.
|
||||
*/
|
||||
// 检查是否给定了槽,如果给定则使用,否则创建一个新的槽
|
||||
if (slot != NULL)
|
||||
ExecSetSlotDescriptor(slot, cleanTupType);
|
||||
ExecSetSlotDescriptor(slot, cleanTupType);
|
||||
else
|
||||
slot = MakeSingleTupleTableSlot(cleanTupType);
|
||||
slot = MakeSingleTupleTableSlot(cleanTupType);
|
||||
|
||||
/*
|
||||
* Calculate the mapping between the original tuple's attributes and the
|
||||
* "clean" tuple's attributes.
|
||||
*
|
||||
* The "map" is an array of "cleanLength" attribute numbers, i.e. one
|
||||
* entry for every attribute of the "clean" tuple. The value of this entry
|
||||
* is the attribute number of the corresponding attribute of the
|
||||
* "original" tuple. We store zero for any deleted attributes, marking
|
||||
* that a NULL is needed in the output tuple.
|
||||
*/
|
||||
cleanLength = cleanTupType->natts;
|
||||
cleanLength = cleanTupType->natts;
|
||||
|
||||
// 为属性映射数组分配内存,并初始化为 0
|
||||
if (cleanLength > 0) {
|
||||
cleanMap = (AttrNumber*)palloc0(cleanLength * sizeof(AttrNumber));
|
||||
t = list_head(targetList);
|
||||
for (i = 0; i < cleanLength; i++) {
|
||||
cleanMap = (AttrNumber*)palloc0(cleanLength * sizeof(AttrNumber));
|
||||
t = list_head(targetList);
|
||||
for (i = 0; i < cleanLength; i++) {
|
||||
if (cleanTupType->attrs[i]->attisdropped)
|
||||
continue; /* map entry is already zero */
|
||||
continue; // 跳过已删除的属性
|
||||
for (;;) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(t);
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(t);
|
||||
|
||||
t = lnext(t);
|
||||
t = lnext(t);
|
||||
if (!tle->resjunk) {
|
||||
cleanMap[i] = tle->resno;
|
||||
cleanMap[i] = tle->resno;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cleanMap = NULL;
|
||||
cleanMap = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finally create and initialize the JunkFilter struct.
|
||||
*/
|
||||
junkfilter = makeNode(JunkFilter);
|
||||
// 创建并初始化 JunkFilter 结构
|
||||
junkfilter = makeNode(JunkFilter);
|
||||
|
||||
junkfilter->jf_targetList = targetList;
|
||||
junkfilter->jf_cleanTupType = cleanTupType;
|
||||
junkfilter->jf_cleanMap = cleanMap;
|
||||
junkfilter->jf_resultSlot = slot;
|
||||
// 填充 JunkFilter 结构的各个字段
|
||||
junkfilter->jf_targetList = targetList;
|
||||
junkfilter->jf_cleanTupType = cleanTupType;
|
||||
junkfilter->jf_cleanMap = cleanMap;
|
||||
junkfilter->jf_resultSlot = slot;
|
||||
|
||||
return junkfilter;
|
||||
return junkfilter;
|
||||
}
|
||||
|
||||
/*
|
||||
* ExecFindJunkAttribute
|
||||
*
|
||||
* Locate the specified junk attribute in the junk filter's targetlist,
|
||||
* and return its resno. Returns InvalidAttrNumber if not found.
|
||||
* 在垃圾过滤器的目标列表中定位指定的垃圾属性,并返回其 resno。
|
||||
* 如果未找到,则返回 InvalidAttrNumber。
|
||||
*/
|
||||
AttrNumber ExecFindJunkAttribute(JunkFilter* junkfilter, const char* attrName)
|
||||
{
|
||||
|
|
@ -205,8 +158,7 @@ AttrNumber ExecFindJunkAttribute(JunkFilter* junkfilter, const char* attrName)
|
|||
/*
|
||||
* ExecFindJunkPrimaryKeys
|
||||
*
|
||||
* Locate the specified junk attribute in the junk filter's targetlist.
|
||||
* Returns NIL if not found.
|
||||
* 在目标列表中查找 xc_primary_key 垃圾属性,返回包含这些属性表达式的列表
|
||||
*/
|
||||
List* ExecFindJunkPrimaryKeys(List* targetlist)
|
||||
{
|
||||
|
|
@ -228,8 +180,7 @@ List* ExecFindJunkPrimaryKeys(List* targetlist)
|
|||
/*
|
||||
* ExecFindJunkAttributeInTlist
|
||||
*
|
||||
* Find a junk attribute given a subplan's targetlist (not necessarily
|
||||
* part of a JunkFilter).
|
||||
* 在目标列表中查找指定名称的垃圾属性,返回属性的编号
|
||||
*/
|
||||
AttrNumber ExecFindJunkAttributeInTlist(List* targetlist, const char* attrName)
|
||||
{
|
||||
|
|
@ -250,9 +201,7 @@ AttrNumber ExecFindJunkAttributeInTlist(List* targetlist, const char* attrName)
|
|||
/*
|
||||
* ExecGetJunkAttribute
|
||||
*
|
||||
* Given a junk filter's input tuple (slot) and a junk attribute's number
|
||||
* previously found by ExecFindJunkAttribute, extract & return the value and
|
||||
* isNull flag of the attribute.
|
||||
* 从元组插槽中获取指定编号的垃圾属性的值
|
||||
*/
|
||||
Datum ExecGetJunkAttribute(TupleTableSlot* slot, AttrNumber attno, bool* isNull)
|
||||
{
|
||||
|
|
@ -265,7 +214,7 @@ Datum ExecGetJunkAttribute(TupleTableSlot* slot, AttrNumber attno, bool* isNull)
|
|||
/*
|
||||
* ExecFilterJunk
|
||||
*
|
||||
* Construct and return a slot with all the junk attributes removed.
|
||||
* 根据垃圾属性映射,构建新的元组插槽,移除垃圾属性,并返回新的插槽。
|
||||
*/
|
||||
TupleTableSlot* ExecFilterJunk(JunkFilter* junkfilter, TupleTableSlot* slot)
|
||||
{
|
||||
|
|
@ -279,34 +228,24 @@ TupleTableSlot* ExecFilterJunk(JunkFilter* junkfilter, TupleTableSlot* slot)
|
|||
Datum* old_values = NULL;
|
||||
bool* old_isnull = NULL;
|
||||
|
||||
/*
|
||||
* Extract all the values of the old tuple.
|
||||
*/
|
||||
|
||||
/* Get the Table Accessor Method*/
|
||||
// 从原始元组中提取所有属性值
|
||||
Assert(slot != NULL && slot->tts_tupleDescriptor != NULL);
|
||||
tableam_tslot_getallattrs(slot);
|
||||
old_values = slot->tts_values;
|
||||
old_isnull = slot->tts_isnull;
|
||||
|
||||
/*
|
||||
* get info from the junk filter
|
||||
*/
|
||||
// 获取 JunkFilter 中的信息
|
||||
cleanTupType = junkfilter->jf_cleanTupType;
|
||||
cleanLength = cleanTupType->natts;
|
||||
cleanMap = junkfilter->jf_cleanMap;
|
||||
resultSlot = junkfilter->jf_resultSlot;
|
||||
|
||||
/*
|
||||
* Prepare to build a virtual result tuple.
|
||||
*/
|
||||
// 准备构建虚拟结果元组
|
||||
(void)ExecClearTuple(resultSlot);
|
||||
values = resultSlot->tts_values;
|
||||
isnull = resultSlot->tts_isnull;
|
||||
|
||||
/*
|
||||
* Transpose data into proper fields of the new tuple.
|
||||
*/
|
||||
// 转置数据到新元组的适当字段中
|
||||
for (i = 0; i < cleanLength; i++) {
|
||||
int j = cleanMap[i];
|
||||
|
||||
|
|
@ -319,95 +258,102 @@ TupleTableSlot* ExecFilterJunk(JunkFilter* junkfilter, TupleTableSlot* slot)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* And return the virtual tuple.
|
||||
*/
|
||||
// 返回过滤后的虚拟元组
|
||||
return ExecStoreVirtualTuple(resultSlot);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* BatchExecFilterJunk
|
||||
*
|
||||
* Construct and return a vector batch with all the junk attributes removed.
|
||||
* 构建并返回一个向量批处理,其中移除了所有垃圾属性。
|
||||
*/
|
||||
VectorBatch* BatchExecFilterJunk(_in_ JunkFilter* junkfilter, __inout VectorBatch* batch)
|
||||
{
|
||||
AttrNumber* cleanMap = NULL;
|
||||
TupleDesc cleanTupType;
|
||||
int cleanLength;
|
||||
int i;
|
||||
ScalarVector* columns = NULL;
|
||||
AttrNumber* cleanMap = NULL; // 属性映射数组,将清理后的属性编号映射到原始属性编号
|
||||
TupleDesc cleanTupType; // 清理后元组的描述符
|
||||
int cleanLength; // 清理后元组的属性数量
|
||||
int i; // 循环计数变量
|
||||
ScalarVector* columns = NULL; // 存储列向量的数组
|
||||
|
||||
// Get info from the junk filter
|
||||
//
|
||||
// 获取 JunkFilter 中的信息
|
||||
cleanTupType = junkfilter->jf_cleanTupType;
|
||||
cleanLength = cleanTupType->natts;
|
||||
cleanMap = junkfilter->jf_cleanMap;
|
||||
columns = batch->m_arr;
|
||||
columns = batch->m_arr; // 获取列向量的数组
|
||||
|
||||
// Transpose data into proper fields of the new tuple.
|
||||
//
|
||||
// 转置数据到新元组的适当字段中
|
||||
for (i = 0; i < cleanLength; i++) {
|
||||
int j = cleanMap[i];
|
||||
|
||||
if (j == 0) {
|
||||
for (int k = 0; k < columns[i].m_rows; k++) {
|
||||
columns[i].SetNull(k);
|
||||
columns[i].SetNull(k); // 将该列向量的元素设置为 NULL
|
||||
}
|
||||
} else {
|
||||
columns[i] = columns[j - 1];
|
||||
columns[i] = columns[j - 1]; // 将原始属性的列向量复制到新属性列向量
|
||||
}
|
||||
}
|
||||
|
||||
// Return the modified batch without changing the column count
|
||||
// as the column count is early decided at compile time.
|
||||
//
|
||||
// 返回修改后的批处理数据,列数不变
|
||||
return batch;
|
||||
}
|
||||
|
||||
/*
|
||||
*ExecSetjunkFilteDescriptor
|
||||
*
|
||||
*设置垃圾过滤器结果插槽的元组描述符,以匹配新的元组描述符。
|
||||
*将给定的 TupleDesc(元组描述符)中的属性信息转置到 JunkFilter 中的结果槽的元组描述符中。在循环中,根据属性映射关系,将属性类型赋值给结果槽的元组描述符,以便在后续操作中使用。
|
||||
*/
|
||||
void ExecSetjunkFilteDescriptor(JunkFilter* junkfilter, TupleDesc tupdesc)
|
||||
{
|
||||
TupleDesc resultslotTupType;
|
||||
AttrNumber* cleanMap = NULL;
|
||||
int cleanLength;
|
||||
int i;
|
||||
TupleDesc resultslotTupType; // 结果槽的元组描述符
|
||||
AttrNumber* cleanMap = NULL; // 属性映射数组,将清理后的属性编号映射到原始属性编号
|
||||
int cleanLength; // 清理后元组的属性数量
|
||||
int i; // 循环计数变量
|
||||
|
||||
cleanLength = junkfilter->jf_cleanTupType->natts;
|
||||
cleanMap = junkfilter->jf_cleanMap;
|
||||
cleanLength = junkfilter->jf_cleanTupType->natts; // 获取清理后元组的属性数量
|
||||
cleanMap = junkfilter->jf_cleanMap; // 获取属性映射数组
|
||||
|
||||
resultslotTupType = junkfilter->jf_resultSlot->tts_tupleDescriptor;
|
||||
resultslotTupType = junkfilter->jf_resultSlot->tts_tupleDescriptor; // 获取结果槽的元组描述符
|
||||
|
||||
/*
|
||||
* Transpose tupdesc into proper fields of the new tupdesc.
|
||||
* 转置 tupdesc 的属性信息到新元组描述符的适当字段中。
|
||||
*/
|
||||
for (i = 0; i < cleanLength; i++) {
|
||||
int j = cleanMap[i];
|
||||
|
||||
// 如果属性映射不为 0,则将 tupdesc 的属性类型赋值给结果槽的元组描述符
|
||||
if (j > 0)
|
||||
resultslotTupType->attrs[i]->atttypid = tupdesc->attrs[j - 1]->atttypid;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @Description: Check if junk attribute xc_node_id is the same as current node identifier
|
||||
*
|
||||
* @param[IN] junkfilter: junk attributes
|
||||
* @param[IN] batch: vector batch
|
||||
* @return: void
|
||||
|
||||
/*BatchCheckNodeIdentifier
|
||||
*
|
||||
* 检查向量批处理中的 `xc_node_id` 垃圾属性,确保其值与当前节点标识符相匹配,用于更新或删除操作的节点标识验证。
|
||||
*/
|
||||
void BatchCheckNodeIdentifier(JunkFilter* junkfilter, VectorBatch* batch)
|
||||
{
|
||||
ScalarVector* xc_node_id_col = NULL;
|
||||
uint32 xc_node_id = 0;
|
||||
int counter = 0;
|
||||
ScalarVector* xc_node_id_col = NULL; // 用于存储 xc_node_id 的列向量
|
||||
uint32 xc_node_id = 0; // 存储当前 xc_node_id
|
||||
int counter = 0; // 循环计数变量
|
||||
|
||||
// 如果 xc_node_id 无效,则直接返回
|
||||
if (InvalidAttrNumber == junkfilter->jf_xc_node_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 xc_node_id 列向量
|
||||
xc_node_id_col = &(batch->m_arr[junkfilter->jf_xc_node_id - 1]);
|
||||
|
||||
// 遍历 xc_node_id 列向量中的值
|
||||
for (counter = 0; counter < xc_node_id_col->m_rows; counter++) {
|
||||
// 获取当前 xc_node_id 的值
|
||||
xc_node_id = DatumGetUInt32(xc_node_id_col->m_vals[counter]);
|
||||
|
||||
// 检查当前 xc_node_id 是否与当前节点的标识不匹配,如果不匹配则抛出错误
|
||||
if (u_sess->pgxc_cxt.PGXCNodeIdentifier != xc_node_id) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
|
|
@ -418,3 +364,4 @@ void BatchCheckNodeIdentifier(JunkFilter* junkfilter, VectorBatch* batch)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,22 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
* 版权所有 (c) 2020 华为技术有限公司
|
||||
*
|
||||
* 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:
|
||||
* openGauss 在 Mulan PSL v2 许可下发布。
|
||||
* 您可以根据 Mulan PSL v2 的条款和条件使用本软件。
|
||||
* 您可以在以下网址获取 Mulan PSL v2 的副本:
|
||||
*
|
||||
* 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.
|
||||
* 本软件按"原样"提供,不提供任何明示或暗示的保证,
|
||||
* 包括但不限于不侵权、适销性或特定用途适用性的保证。
|
||||
* 有关更多详细信息,请参阅 Mulan PSL v2。
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* execMerge.cpp
|
||||
* routines to handle Merge nodes relating to the MERGE command
|
||||
* 处理与 MERGE 命令相关的 Merge 节点的函数
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* 标识符
|
||||
* src/gausskernel/runtime/executor/execMerge.cpp
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
|
|
@ -39,11 +38,13 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple
|
|||
static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, JunkFilter* junkfilter,
|
||||
ItemPointer tupleid, HeapTupleHeader oldtuple, Oid oldPartitionOid, int2 bucketid);
|
||||
/*
|
||||
* Perform MERGE.
|
||||
* 执行 MERGE 操作
|
||||
*/
|
||||
|
||||
void ExecMerge(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, JunkFilter* junkfilter,
|
||||
ResultRelInfo* resultRelInfo)
|
||||
{
|
||||
// 获取执行上下文
|
||||
ExprContext* econtext = mtstate->ps.ps_ExprContext;
|
||||
ItemPointer tupleid;
|
||||
ItemPointerData tuple_ctid;
|
||||
|
|
@ -56,34 +57,26 @@ void ExecMerge(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot,
|
|||
AttrNumber bucketIdNum;
|
||||
int2 bucketid = InvalidBktId;
|
||||
|
||||
// 检查结果关系类型和垃圾过滤器
|
||||
Assert(resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION ||
|
||||
resultRelInfo->ri_RelationDesc->rd_rel->relkind == PARTTYPE_PARTITIONED_RELATION ||
|
||||
junkfilter != NULL);
|
||||
resultRelInfo->ri_RelationDesc->rd_rel->relkind == PARTTYPE_PARTITIONED_RELATION ||
|
||||
junkfilter != NULL);
|
||||
|
||||
/*
|
||||
* Reset per-tuple memory context to free any expression evaluation
|
||||
* storage allocated in the previous cycle.
|
||||
* 重置每个元组内存上下文,以释放在前一个周期中分配的任何表达式评估存储空间。
|
||||
*/
|
||||
ResetExprContext(econtext);
|
||||
|
||||
/*
|
||||
* We run a JOIN between the target relation and the source relation to
|
||||
* find a set of candidate source rows that has matching row in the target
|
||||
* table and a set of candidate source rows that does not have matching
|
||||
* row in the target table. If the join returns us a tuple with target
|
||||
* relation's tid set, that implies that the join found a matching row for
|
||||
* the given source tuple. This case triggers the WHEN MATCHED clause of
|
||||
* the MERGE. Whereas a NULL in the target relation's ctid column
|
||||
* indicates a NOT MATCHED case.
|
||||
*/
|
||||
// 从槽中提取关于匹配情况的信息
|
||||
datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, &isNull);
|
||||
|
||||
if (!isNull) {
|
||||
matched = true;
|
||||
tupleid = (ItemPointer)DatumGetPointer(datum);
|
||||
tuple_ctid = *tupleid; /* be sure we don't free ctid!! */
|
||||
tuple_ctid = *tupleid;/* 确保我们不释放 ctid!! */
|
||||
tupleid = &tuple_ctid;
|
||||
|
||||
// 处理分区表和分桶表的情况
|
||||
if (RELATION_IS_PARTITIONED(resultRelInfo->ri_RelationDesc) ||
|
||||
RelationIsCUFormat(resultRelInfo->ri_RelationDesc)) {
|
||||
Datum tableOiddatum;
|
||||
|
|
@ -114,67 +107,54 @@ void ExecMerge(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot,
|
|||
bucketid = DatumGetObjectId(bucketIddatum);
|
||||
}
|
||||
|
||||
} else {
|
||||
matched = false;
|
||||
tupleid = NULL; /* we don't need it for INSERT actions */
|
||||
}
|
||||
|
||||
else {
|
||||
matched = false;
|
||||
tupleid = NULL; /* 对于 INSERT 操作,不需要这个信息 */
|
||||
}
|
||||
/*
|
||||
* If we are dealing with a WHEN MATCHED case, we execute the first action
|
||||
* for which the additional WHEN MATCHED AND quals pass. If an action
|
||||
* without quals is found, that action is executed.
|
||||
*
|
||||
* Similarly, if we are dealing with WHEN NOT MATCHED case, we look at the
|
||||
* given WHEN NOT MATCHED actions in sequence until one passes.
|
||||
*
|
||||
* Things get interesting in case of concurrent update/delete of the
|
||||
* target tuple. Such concurrent update/delete is detected while we are
|
||||
* executing a WHEN MATCHED action.
|
||||
*
|
||||
* A concurrent update can:
|
||||
*
|
||||
* 1. modify the target tuple so that it no longer satisfies the
|
||||
* additional quals attached to the current WHEN MATCHED action OR
|
||||
*
|
||||
* In this case, we are still dealing with a WHEN MATCHED case, but
|
||||
* we should recheck the list of WHEN MATCHED actions and choose the first
|
||||
* one that satisfies the new target tuple.
|
||||
*
|
||||
* 2. modify the target tuple so that the join quals no longer pass and
|
||||
* hence the source tuple no longer has a match.
|
||||
*
|
||||
* In the second case, the source tuple no longer matches the target tuple,
|
||||
* so we now instead find a qualifying WHEN NOT MATCHED action to execute.
|
||||
*
|
||||
* A concurrent delete, changes a WHEN MATCHED case to WHEN NOT MATCHED.
|
||||
*
|
||||
* ExecMergeMatched takes care of following the update chain and
|
||||
* re-finding the qualifying WHEN MATCHED action, as long as the updated
|
||||
* target tuple still satisfies the join quals i.e. it still remains a
|
||||
* WHEN MATCHED case. If the tuple gets deleted or the join quals fail, it
|
||||
* returns and we try ExecMergeNotMatched. Given that ExecMergeMatched
|
||||
* always make progress by following the update chain and we never switch
|
||||
* from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a
|
||||
* livelock.
|
||||
*/
|
||||
* 如果我们处理的是 WHEN MATCHED 情况,我们执行第一个附加的 WHEN MATCHED AND 条件满足的动作。
|
||||
* 如果找到一个没有附加条件的动作,则执行该动作。
|
||||
*
|
||||
* 类似地,如果我们处理的是 WHEN NOT MATCHED 情况,我们按顺序查看给定的 WHEN NOT MATCHED 动作,直到找到一个满足条件的动作为止。
|
||||
*
|
||||
* 在处理 WHEN MATCHED 情况时,同时进行目标元组的并发更新/删除会变得有趣。
|
||||
*
|
||||
* 并发更新可能会有以下情况:
|
||||
*
|
||||
* 1. 修改目标元组,使其不再满足当前 WHEN MATCHED 动作附加的附加条件。
|
||||
*
|
||||
* 在这种情况下,我们仍然处理 WHEN MATCHED 情况,但应重新检查 WHEN MATCHED 动作列表,并选择满足新目标元组的第一个动作。
|
||||
*
|
||||
* 2. 修改目标元组,使联接条件不再满足,因此源元组不再匹配。
|
||||
*
|
||||
* 在第二种情况下,源元组不再与目标元组匹配,因此我们现在会找到一个满足条件的 WHEN NOT MATCHED 动作来执行。
|
||||
*
|
||||
* 并发删除将 WHEN MATCHED 情况更改为 WHEN NOT MATCHED。
|
||||
*
|
||||
* ExecMergeMatched 负责遵循更新链并重新查找满足条件的 WHEN MATCHED 动作,只要更新的目标元组仍然满足联接条件,即仍然是 WHEN MATCHED 情况。
|
||||
* 如果元组被删除或联接条件失败,则返回并尝试 ExecMergeNotMatched。鉴于 ExecMergeMatched 总是通过跟踪更新链来取得进展,
|
||||
* 我们永远不会从 ExecMergeNotMatched 切换到 ExecMergeMatched,因此不会出现死锁的风险。
|
||||
*/
|
||||
|
||||
// 根据匹配情况执行相应的动作
|
||||
if (matched)
|
||||
matched = ExecMergeMatched(mtstate, estate, slot, junkfilter, tupleid, oldtuple, oldPartitionOid, bucketid);
|
||||
|
||||
/*
|
||||
* Either we were dealing with a NOT MATCHED tuple or ExecMergeNotMatched()
|
||||
* returned "false", indicating the previously MATCHED tuple is no longer a
|
||||
* matching tuple.
|
||||
*/
|
||||
// 如果没有匹配的情况,执行相应的 NOT MATCHED 操作
|
||||
if (!matched)
|
||||
ExecMergeNotMatched(mtstate, estate, slot);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Extract tuple for checking constraints from plan slot
|
||||
* 从计划槽中提取元组以进行约束检查
|
||||
*/
|
||||
static TupleTableSlot* ExtractConstraintTuple(
|
||||
ModifyTableState* mtstate, CmdType commandType, TupleTableSlot* slot, TupleDesc tupDesc)
|
||||
{
|
||||
// 获取执行上下文
|
||||
ExprContext* econtext = mtstate->ps.ps_ExprContext;
|
||||
AutoContextSwitch memContext(econtext->ecxt_per_tuple_memory);
|
||||
HeapTuple tempTuple = NULL;
|
||||
|
|
@ -185,40 +165,47 @@ static TupleTableSlot* ExtractConstraintTuple(
|
|||
int index = 0;
|
||||
int i = 0;
|
||||
|
||||
// 根据命令类型提取约束元组的槽
|
||||
switch (commandType) {
|
||||
case CMD_UPDATE:
|
||||
constrSlot = mtstate->mt_update_constr_slot;
|
||||
for (i = 0; i < originTupleDesc->natts; i++) {
|
||||
if (strstr(originTupleDesc->attrs[i]->attname.data, "action UPDATE target")) {
|
||||
values[index] = slot->tts_values[i];
|
||||
isnull[index] = slot->tts_isnull[i];
|
||||
index++;
|
||||
}
|
||||
case CMD_UPDATE:
|
||||
constrSlot = mtstate->mt_update_constr_slot;
|
||||
for (i = 0; i < originTupleDesc->natts; i++) {
|
||||
// 查找符合条件的属性并复制值和空标志
|
||||
if (strstr(originTupleDesc->attrs[i]->attname.data, "action UPDATE target")) {
|
||||
values[index] = slot->tts_values[i];
|
||||
isnull[index] = slot->tts_isnull[i];
|
||||
index++;
|
||||
}
|
||||
break;
|
||||
case CMD_INSERT:
|
||||
constrSlot = mtstate->mt_insert_constr_slot;
|
||||
for (i = 0; i < originTupleDesc->natts; i++) {
|
||||
if (strstr(originTupleDesc->attrs[i]->attname.data, "action INSERT target")) {
|
||||
values[index] = slot->tts_values[i];
|
||||
isnull[index] = slot->tts_isnull[i];
|
||||
index++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CMD_INSERT:
|
||||
constrSlot = mtstate->mt_insert_constr_slot;
|
||||
for (i = 0; i < originTupleDesc->natts; i++) {
|
||||
// 查找符合条件的属性并复制值和空标志
|
||||
if (strstr(originTupleDesc->attrs[i]->attname.data, "action INSERT target")) {
|
||||
values[index] = slot->tts_values[i];
|
||||
isnull[index] = slot->tts_isnull[i];
|
||||
index++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Assert(0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Assert(0);
|
||||
}
|
||||
|
||||
// 确保约束槽的表访问方法类型与原始元组描述一致
|
||||
Assert(constrSlot->tts_tupleDescriptor->tdTableAmType == originTupleDesc->tdTableAmType);
|
||||
|
||||
// 使用 values 和 isnull 数组创建临时 HeapTuple,并将其存储到约束槽中
|
||||
tempTuple = (HeapTuple)tableam_tops_form_tuple(tupDesc, values, isnull, HEAP_TUPLE);
|
||||
(void)ExecStoreTuple(tempTuple, constrSlot, InvalidBuffer, false);
|
||||
|
||||
return constrSlot;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Extract scan tuple for target table from plan slot
|
||||
* 从计划槽中提取目标表的扫描元组
|
||||
*/
|
||||
TupleTableSlot* ExtractScanTuple(ModifyTableState* mtstate, TupleTableSlot* slot, TupleDesc tupDesc)
|
||||
{
|
||||
|
|
@ -234,18 +221,18 @@ TupleTableSlot* ExtractScanTuple(ModifyTableState* mtstate, TupleTableSlot* slot
|
|||
int index = 0;
|
||||
|
||||
/*
|
||||
* Find the right start index for target table. We should skip the sourceTargetList.
|
||||
* First count the number of source targetlist. We add new columns to sourceTargetList
|
||||
* but the resno is not continuous, so find the max continuous number to be the original
|
||||
* length of sourceTargetList.
|
||||
* 找到目标表的正确起始索引。我们应该跳过 sourceTargetList。
|
||||
* 首先计算 sourceTargetList 中源列的数量。虽然我们向 sourceTargetList 添加了新列,
|
||||
* 但 resno 不是连续的,因此找到最大的连续编号作为 sourceTargetList 的原始长度。
|
||||
*/
|
||||
foreach (lc, sourceTargetList) {
|
||||
foreach(lc, sourceTargetList) {
|
||||
TargetEntry* tle = (TargetEntry*)lfirst(lc);
|
||||
if (tle->resno != startIdx + 1)
|
||||
break;
|
||||
startIdx++;
|
||||
}
|
||||
|
||||
// 从原始槽中提取值和空标志,并构建一个临时 HeapTuple
|
||||
for (index = 0; index < tupDesc->natts; index++) {
|
||||
if (tupDesc->attrs[index]->attisdropped == true) {
|
||||
isnull[index] = true;
|
||||
|
|
@ -257,6 +244,7 @@ TupleTableSlot* ExtractScanTuple(ModifyTableState* mtstate, TupleTableSlot* slot
|
|||
startIdx++;
|
||||
}
|
||||
|
||||
// 使用 values 和 isnull 数组创建临时 HeapTuple,并将其存储到扫描槽中
|
||||
tempTuple = (HeapTuple)tableam_tops_form_tuple(tupDesc, values, isnull, HEAP_TUPLE);
|
||||
(void)ExecStoreTuple(tempTuple, scanSlot, InvalidBuffer, false);
|
||||
|
||||
|
|
@ -264,15 +252,15 @@ TupleTableSlot* ExtractScanTuple(ModifyTableState* mtstate, TupleTableSlot* slot
|
|||
}
|
||||
|
||||
/*
|
||||
* Description: projects and evaluates qual condition for update action.
|
||||
* Parameters:
|
||||
* @in mtstate: modifytable state.
|
||||
* @in mergeMatchedActionStates: update action states.
|
||||
* @in econtext: expression context.
|
||||
* @in originSlot: slot to be projected.
|
||||
* @in result_slot: slot to be returned.
|
||||
* @in estate: working state for executor.
|
||||
* Return: slot has been projected..
|
||||
* 描述:对更新操作进行投影和评估条件。
|
||||
* 参数:
|
||||
* @in mtstate:modifytable 状态。
|
||||
* @in mergeMatchedActionStates:更新操作状态。
|
||||
* @in econtext:表达式上下文。
|
||||
* @in originSlot:待投影的槽。
|
||||
* @in result_slot:将要返回的槽。
|
||||
* @in estate:执行器的工作状态。
|
||||
* 返回:已投影的槽。
|
||||
*/
|
||||
TupleTableSlot* ExecMergeProjQual(ModifyTableState* mtstate, List* mergeMatchedActionStates, ExprContext* econtext,
|
||||
TupleTableSlot* originSlot, TupleTableSlot* result_slot, EState* estate)
|
||||
|
|
@ -285,55 +273,51 @@ TupleTableSlot* ExecMergeProjQual(ModifyTableState* mtstate, List* mergeMatchedA
|
|||
Assert(CMD_UPDATE == action->commandType);
|
||||
|
||||
/*
|
||||
* get information on the (current) result relation
|
||||
*/
|
||||
* 获取关于(当前)结果关系的信息
|
||||
*/
|
||||
resultRelInfo = estate->es_result_relation_info;
|
||||
resultRelationDesc = resultRelInfo->ri_RelationDesc;
|
||||
|
||||
/*
|
||||
* Make tuple and any needed join variables available to ExecQual and
|
||||
* ExecProject. The target's existing tuple is installed in the scantuple.
|
||||
* Again, this target relation's slot is required only in the case of a
|
||||
* MATCHED tuple and UPDATE/DELETE actions.
|
||||
*/
|
||||
* 使元组和任何必要的连接变量对 ExecQual 和 ExecProject 可用。
|
||||
* 目标的现有元组被安装在 scantuple 中。
|
||||
* 同样,在匹配的元组和 UPDATE/DELETE 操作的情况下,仅需要此目标关系的槽。
|
||||
*/
|
||||
if (estate->es_result_update_remoterel == NULL) {
|
||||
econtext->ecxt_scantuple = ExtractScanTuple(mtstate, originSlot, action->tupDesc);
|
||||
econtext->ecxt_innertuple = originSlot;
|
||||
econtext->ecxt_outertuple = NULL;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
econtext->ecxt_scantuple = originSlot;
|
||||
econtext->ecxt_innertuple = NULL;
|
||||
econtext->ecxt_outertuple = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test condition, if any
|
||||
*
|
||||
* In the absence of a condition we perform the action unconditionally
|
||||
* (no need to check separately since ExecQual() will return true if
|
||||
* there are no conditions to evaluate).
|
||||
*/
|
||||
* 测试条件,如果有的话
|
||||
*
|
||||
* 在没有条件的情况下,我们无条件执行动作
|
||||
* (无需单独检查,因为如果没有条件要评估,ExecQual() 将返回 true)。
|
||||
*/
|
||||
if (ExecQual((List*)action->whenqual, econtext, false)) {
|
||||
if (estate->es_result_update_remoterel == NULL) {
|
||||
/*
|
||||
* We set up the projection earlier, so all we do here is
|
||||
* Project, no need for any other tasks prior to the
|
||||
* ExecUpdate.
|
||||
*/
|
||||
* 我们之前已经设置了投影,所以这里我们只需要执行投影,不需要在执行 ExecUpdate 之前进行任何其他任务。
|
||||
*/
|
||||
result_slot = ExecProject(action->proj, NULL);
|
||||
} else {
|
||||
/* we don't do projection in remote query */
|
||||
}
|
||||
else {
|
||||
/* 在远程查询中我们不进行投影操作 */
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't call ExecFilterJunk() because the projected tuple
|
||||
* using the UPDATE action's targetlist doesn't have a junk
|
||||
* attribute.
|
||||
* 我们不调用 ExecFilterJunk(),因为使用 UPDATE 操作的目标列表投影的元组没有垃圾属性。
|
||||
*/
|
||||
if (estate->es_result_update_remoterel) {
|
||||
estate->es_result_remoterel = estate->es_result_update_remoterel;
|
||||
|
||||
/* Check if has constraints */
|
||||
/* 检查是否有约束条件 */
|
||||
if (resultRelationDesc->rd_att->constr) {
|
||||
mtstate->mt_update_constr_slot =
|
||||
ExtractConstraintTuple(mtstate, CMD_UPDATE, result_slot, action->tupDesc);
|
||||
|
|
@ -347,25 +331,20 @@ TupleTableSlot* ExecMergeProjQual(ModifyTableState* mtstate, List* mergeMatchedA
|
|||
}
|
||||
|
||||
/*
|
||||
* Check and execute the first qualifying MATCHED action. The current target
|
||||
* tuple is identified by tupleid.
|
||||
* 检查并执行第一个符合条件的 MATCHED 动作。当前的目标元组由 tupleid 标识。
|
||||
*
|
||||
* We start from the first WHEN MATCHED action and check if the WHEN AND quals
|
||||
* pass, if any. If the WHEN AND quals for the first action do not pass, we
|
||||
* check the second, then the third and so on. If we reach to the end, no
|
||||
* action is taken and we return true, indicating that no further action is
|
||||
* required for this tuple.
|
||||
* 我们从第一个 WHEN MATCHED 动作开始,检查是否通过了相应的 WHEN AND 条件,如果有的话。
|
||||
* 如果第一个动作的 WHEN AND 条件不满足,我们检查第二个动作,然后是第三个,依此类推。
|
||||
* 如果我们达到了最后一个动作,表示没有采取任何操作,我们返回 true,表示此元组无需进一步的操作。
|
||||
*
|
||||
* If we do find a qualifying action, then we attempt to execute the action.
|
||||
* 如果我们找到了符合条件的动作,那么我们尝试执行该动作。
|
||||
*
|
||||
* If the tuple is concurrently updated, EvalPlanQual is run with the updated
|
||||
* tuple to recheck the join quals. Note that the additional quals associated
|
||||
* with individual actions are evaluated separately by the MERGE code, while
|
||||
* EvalPlanQual checks for the join quals. If EvalPlanQual tells us that the
|
||||
* updated tuple still passes the join quals, then we restart from the first
|
||||
* action to look for a qualifying action. Otherwise, we return false meaning
|
||||
* that a NOT MATCHED action must now be executed for the current source tuple.
|
||||
* 如果元组正在并发更新,将使用更新后的元组运行 EvalPlanQual 来重新检查联接条件。
|
||||
* 需要注意的是,与各个动作关联的附加条件由 MERGE 代码单独评估,而 EvalPlanQual 则检查联接条件。
|
||||
* 如果 EvalPlanQual 告诉我们更新后的元组仍然满足联接条件,那么我们从第一个动作重新开始寻找符合条件的动作。
|
||||
* 否则,我们返回 false,意味着现在必须为当前的源元组执行一个 NOT MATCHED 动作。
|
||||
*/
|
||||
|
||||
static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, JunkFilter* junkfilter,
|
||||
ItemPointer tupleid, HeapTupleHeader oldtuple, Oid oldPartitionOid, int2 bucketid)
|
||||
{
|
||||
|
|
@ -378,13 +357,13 @@ static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTab
|
|||
bool partKeyUpdated = ((ModifyTable*)mtstate->ps.plan)->partKeyUpdated;
|
||||
|
||||
/*
|
||||
* Save the current information and work with the correct result relation.
|
||||
* 保存当前的信息并切换到正确的结果关系进行操作。
|
||||
*/
|
||||
saved_resultRelInfo = resultRelInfo;
|
||||
estate->es_result_relation_info = resultRelInfo;
|
||||
|
||||
/*
|
||||
* And get the correct action lists.
|
||||
* 获取正确的动作列表。
|
||||
*/
|
||||
mergeMatchedActionStates = resultRelInfo->ri_mergeState->matchedActionStates;
|
||||
|
||||
|
|
@ -395,34 +374,33 @@ static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTab
|
|||
|
||||
if (slot != NULL) {
|
||||
(void)ExecUpdate(tupleid,
|
||||
oldPartitionOid,
|
||||
bucketid,
|
||||
oldtuple,
|
||||
slot,
|
||||
saved_slot,
|
||||
epqstate,
|
||||
mtstate,
|
||||
mtstate->canSetTag,
|
||||
partKeyUpdated);
|
||||
oldPartitionOid,
|
||||
bucketid,
|
||||
oldtuple,
|
||||
slot,
|
||||
saved_slot,
|
||||
epqstate,
|
||||
mtstate,
|
||||
mtstate->canSetTag,
|
||||
partKeyUpdated);
|
||||
}
|
||||
if (action->commandType == CMD_UPDATE /* && tuple_updated*/)
|
||||
InstrCountFiltered2(&mtstate->ps, 1);
|
||||
|
||||
/*
|
||||
* We've activated one of the WHEN clauses, so we don't search
|
||||
* further. This is required behaviour, not an optimization.
|
||||
*/
|
||||
* 我们已经触发了 WHEN 子句中的一个,因此无需继续搜索。这是所需的行为,而不是优化。
|
||||
*/
|
||||
estate->es_result_relation_info = saved_resultRelInfo;
|
||||
}
|
||||
|
||||
/*
|
||||
* Successfully executed an action or no qualifying action was found.
|
||||
* 成功执行了一个动作,或者没有找到符合条件的动作。
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute the first qualifying NOT MATCHED action.
|
||||
* 执行第一个符合条件的 NOT MATCHED 动作。
|
||||
*/
|
||||
static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot)
|
||||
{
|
||||
|
|
@ -433,31 +411,26 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple
|
|||
const int hi_options = 0;
|
||||
|
||||
/*
|
||||
* We are dealing with NOT MATCHED tuple. Since for MERGE, the partition
|
||||
* tree is not expanded for the result relation, we continue to work with
|
||||
* the currently active result relation, which corresponds to the root
|
||||
* of the partition tree.
|
||||
* 我们正在处理 NOT MATCHED 元组。由于对于 MERGE,分区树未对结果关系展开,因此我们继续使用当前活动的结果关系,
|
||||
* 这对应于分区树的根节点。
|
||||
*/
|
||||
resultRelInfo = mtstate->resultRelInfo;
|
||||
|
||||
/*
|
||||
* For INSERT actions, root relation's merge action is OK since the
|
||||
* INSERT's targetlist and the WHEN conditions can only refer to the
|
||||
* source relation and hence it does not matter which result relation we
|
||||
* work with.
|
||||
* 对于 INSERT 操作,根关系的合并操作是可以的,因为 INSERT 的目标列表和 WHEN 条件只能引用源关系,
|
||||
* 因此我们使用哪个结果关系并不重要。
|
||||
*/
|
||||
mergeNotMatchedActionStates = resultRelInfo->ri_mergeState->notMatchedActionStates;
|
||||
|
||||
/*
|
||||
* Make source tuple available to ExecQual and ExecProject. We don't need
|
||||
* the target tuple since the WHEN quals and the targetlist can't refer to
|
||||
* the target columns.
|
||||
* 使源元组对 ExecQual 和 ExecProject 可用。我们不需要目标元组,因为 WHEN 条件和目标列表不能引用目标列。
|
||||
*/
|
||||
if (estate->es_result_insert_remoterel == NULL) {
|
||||
econtext->ecxt_scantuple = slot;
|
||||
econtext->ecxt_innertuple = slot;
|
||||
econtext->ecxt_outertuple = NULL;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
econtext->ecxt_scantuple = slot;
|
||||
econtext->ecxt_innertuple = NULL;
|
||||
econtext->ecxt_outertuple = NULL;
|
||||
|
|
@ -471,36 +444,33 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple
|
|||
Assert(CMD_INSERT == action->commandType);
|
||||
|
||||
/*
|
||||
* get information on the (current) result relation
|
||||
* 获取关于(当前)结果关系的信息
|
||||
*/
|
||||
resultRelationInfo = estate->es_result_relation_info;
|
||||
resultRelationDesc = resultRelationInfo->ri_RelationDesc;
|
||||
|
||||
/*
|
||||
* Test condition, if any
|
||||
*
|
||||
* In the absence of a condition we perform the action unconditionally
|
||||
* (no need to check separately since ExecQual() will return true if
|
||||
* there are no conditions to evaluate).
|
||||
*/
|
||||
* 测试条件,如果有的话
|
||||
*
|
||||
* 在没有条件的情况下,我们无条件执行动作
|
||||
* (无需单独检查,因为如果没有条件要评估,ExecQual() 将返回 true)。
|
||||
*/
|
||||
if (ExecQual((List*)action->whenqual, econtext, false)) {
|
||||
/*
|
||||
* We set up the projection earlier, so all we do here is
|
||||
* Project, no need for any other tasks prior to the
|
||||
* ExecInsert.
|
||||
* 我们之前已经设置了投影,所以这里我们只需要执行投影,不需要在执行 ExecInsert 之前进行任何其他任务。
|
||||
*/
|
||||
if (estate->es_result_insert_remoterel == NULL) {
|
||||
ExecProject(action->proj, NULL);
|
||||
/*
|
||||
* ExecPrepareTupleRouting may modify the passed-in slot. Hence
|
||||
* pass a local reference so that action->slot is not modified.
|
||||
* ExecPrepareTupleRouting 可能会修改传入的槽。因此传递一个局部引用,以防止修改 action->slot。
|
||||
*/
|
||||
myslot = mtstate->mt_mergeproj;
|
||||
} else {
|
||||
/* in pgxc we do projection in the remote query*/
|
||||
}
|
||||
else {
|
||||
/* 在 pgxc 中,我们在远程查询中进行投影操作 */
|
||||
myslot = slot;
|
||||
|
||||
/* Check if has constraints */
|
||||
/* 检查是否有约束条件 */
|
||||
if (resultRelationDesc->rd_att->constr) {
|
||||
mtstate->mt_insert_constr_slot = ExtractConstraintTuple(mtstate, CMD_INSERT, slot, action->tupDesc);
|
||||
}
|
||||
|
|
@ -516,7 +486,7 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple
|
|||
}
|
||||
|
||||
/*
|
||||
* Creates the run-time state information for the Merge node
|
||||
* 创建用于 Merge 节点的运行时状态信息
|
||||
*/
|
||||
void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* resultRelInfo)
|
||||
{
|
||||
|
|
@ -527,32 +497,33 @@ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* res
|
|||
TupleDesc relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
|
||||
ModifyTable* node = (ModifyTable*)mtstate->ps.plan;
|
||||
|
||||
// 如果 mergeActionList 为空,则直接返回
|
||||
if (node->mergeActionList == NIL)
|
||||
return;
|
||||
|
||||
mtstate->mt_merge_subcommands = 0;
|
||||
|
||||
// 分配表达式上下文,如果不存在的话
|
||||
if (mtstate->ps.ps_ExprContext == NULL)
|
||||
ExecAssignExprContext(estate, &mtstate->ps);
|
||||
|
||||
econtext = mtstate->ps.ps_ExprContext;
|
||||
|
||||
/* initialize scan slot and constraint slot */
|
||||
// 初始化扫描槽和约束槽
|
||||
mtstate->mt_scan_slot = NULL;
|
||||
mtstate->mt_update_constr_slot = NULL;
|
||||
mtstate->mt_insert_constr_slot = NULL;
|
||||
|
||||
/* initialize slot for merge actions */
|
||||
// 初始化用于合并操作的投影槽
|
||||
Assert(mtstate->mt_mergeproj == NULL);
|
||||
mtstate->mt_mergeproj = ExecInitExtraTupleSlot(mtstate->ps.state);
|
||||
ExecSetSlotDescriptor(mtstate->mt_mergeproj, relationDesc);
|
||||
|
||||
/*
|
||||
* Create a MergeActionState for each action on the mergeActionList
|
||||
* and add it to either a list of matched actions or not-matched
|
||||
* actions.
|
||||
* 为 mergeActionList 上的每个动作创建一个 MergeActionState,
|
||||
* 并将其添加到匹配动作或不匹配动作的列表中。
|
||||
*/
|
||||
foreach (l, node->mergeActionList) {
|
||||
foreach(l, node->mergeActionList) {
|
||||
MergeAction* action = (MergeAction*)lfirst(l);
|
||||
MergeActionState* action_state = makeNode(MergeActionState);
|
||||
TupleDesc tupDesc;
|
||||
|
|
@ -562,10 +533,11 @@ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* res
|
|||
action_state->commandType = action->commandType;
|
||||
action_state->whenqual = ExecInitExpr((Expr*)action->qual, &mtstate->ps);
|
||||
|
||||
/* create target slot for this action's projection */
|
||||
/* 为此动作的投影创建目标槽 */
|
||||
tupDesc = ExecTypeFromTL((List*)action->targetList, false, true, relationDesc->tdTableAmType);
|
||||
action_state->tupDesc = tupDesc;
|
||||
|
||||
// 在特定情况下创建扫描槽和约束槽
|
||||
if (IS_PGXC_DATANODE && CMD_UPDATE == action->commandType) {
|
||||
mtstate->mt_scan_slot = MakeSingleTupleTableSlot(tupDesc);
|
||||
}
|
||||
|
|
@ -578,34 +550,35 @@ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* res
|
|||
mtstate->mt_insert_constr_slot = MakeSingleTupleTableSlot(tupDesc);
|
||||
}
|
||||
|
||||
/* build action projection state */
|
||||
/* 构建动作投影状态 */
|
||||
targetList = (List*)ExecInitExpr((Expr*)action->targetList, &mtstate->ps);
|
||||
action_state->proj = ExecBuildProjectionInfo(targetList, econtext, mtstate->mt_mergeproj, relationDesc);
|
||||
|
||||
/*
|
||||
* We create two lists - one for WHEN MATCHED actions and one
|
||||
* for WHEN NOT MATCHED actions - and stick the
|
||||
* MergeActionState into the appropriate list.
|
||||
* 我们创建两个列表 - 一个用于 WHEN MATCHED 操作,一个用于 WHEN NOT MATCHED 操作 -
|
||||
* 并将 MergeActionState 放入适当的列表中。
|
||||
*/
|
||||
if (action_state->matched)
|
||||
mergeMatchedActionStates = lappend(mergeMatchedActionStates, action_state);
|
||||
else
|
||||
mergeNotMatchedActionStates = lappend(mergeNotMatchedActionStates, action_state);
|
||||
|
||||
// 根据不同的操作类型设置子命令标志
|
||||
switch (action->commandType) {
|
||||
case CMD_INSERT:
|
||||
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, action->targetList);
|
||||
mtstate->mt_merge_subcommands |= MERGE_INSERT;
|
||||
break;
|
||||
case CMD_UPDATE:
|
||||
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, action->targetList);
|
||||
mtstate->mt_merge_subcommands |= MERGE_UPDATE;
|
||||
break;
|
||||
default:
|
||||
Assert(0);
|
||||
break;
|
||||
case CMD_INSERT:
|
||||
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, action->targetList);
|
||||
mtstate->mt_merge_subcommands |= MERGE_INSERT;
|
||||
break;
|
||||
case CMD_UPDATE:
|
||||
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, action->targetList);
|
||||
mtstate->mt_merge_subcommands |= MERGE_UPDATE;
|
||||
break;
|
||||
default:
|
||||
Assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// 设置匹配和不匹配动作的状态列表
|
||||
resultRelInfo->ri_mergeState->matchedActionStates = mergeMatchedActionStates;
|
||||
resultRelInfo->ri_mergeState->notMatchedActionStates = mergeNotMatchedActionStates;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,80 +1,59 @@
|
|||
/* -------------------------------------------------------------------------
|
||||
*
|
||||
* execProcnode.cpp
|
||||
* contains dispatch functions which call the appropriate "initialize",
|
||||
* "get a tuple", and "cleanup" routines for the given node type.
|
||||
* If the node has children, then it will presumably call ExecInitNode,
|
||||
* ExecProcNode, or ExecEndNode on its subnodes and do the appropriate
|
||||
* processing.
|
||||
* 包含调用给定节点类型的适当的 "初始化"、"获取元组" 和 "清理" 程序的调度函数。
|
||||
* 如果节点有子节点,则可能会在其子节点上调用 ExecInitNode、ExecProcNode 或 ExecEndNode,并进行适当的处理。
|
||||
*
|
||||
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
* Portions Copyright (c) 2021, openGauss Contributors
|
||||
* 版权所有 (c) 2020 华为技术有限公司。
|
||||
* 版权所有 (c) 1996-2012 年,PostgreSQL 全球开发团队。
|
||||
* 版权所有 (c) 1994 年,加利福尼亚大学理事会。
|
||||
* 版权所有 (c) 2021 年,openGauss 贡献者。
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* src/gausskernel/runtime/executor/execProcnode.cpp
|
||||
* 标识符
|
||||
* src/gausskernel/runtime/executor/execProcnode.cpp
|
||||
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
/*
|
||||
* INTERFACE ROUTINES
|
||||
* ExecInitNode - initialize a plan node and its subplans
|
||||
* ExecProcNode - get a tuple by executing the plan node
|
||||
* ExecEndNode - shut down a plan node and its subplans
|
||||
*
|
||||
* NOTES
|
||||
* This used to be three files. It is now all combined into
|
||||
* one file so that it is easier to keep ExecInitNode, ExecProcNode,
|
||||
* and ExecEndNode in sync when new nodes are added.
|
||||
*
|
||||
* EXAMPLE
|
||||
* Suppose we want the age of the manager of the shoe department and
|
||||
* the number of employees in that department. So we have the query:
|
||||
*
|
||||
* select DEPT.no_emps, EMP.age
|
||||
* where EMP.name = DEPT.mgr and
|
||||
* DEPT.name = "shoe"
|
||||
*
|
||||
* Suppose the planner gives us the following plan:
|
||||
*
|
||||
* Nest Loop (DEPT.mgr = EMP.name)
|
||||
* / \
|
||||
* / \
|
||||
* Seq Scan Seq Scan
|
||||
* DEPT EMP
|
||||
* (name = "shoe")
|
||||
*
|
||||
* ExecutorStart() is called first.
|
||||
* It calls InitPlan() which calls ExecInitNode() on
|
||||
* the root of the plan -- the nest loop node.
|
||||
*
|
||||
* * ExecInitNode() notices that it is looking at a nest loop and
|
||||
* as the code below demonstrates, it calls ExecInitNestLoop().
|
||||
* Eventually this calls ExecInitNode() on the right and left subplans
|
||||
* and so forth until the entire plan is initialized. The result
|
||||
* of ExecInitNode() is a plan state tree built with the same structure
|
||||
* as the underlying plan tree.
|
||||
*
|
||||
* * Then when ExecutorRun() is called, it calls ExecutePlan() which calls
|
||||
* ExecProcNode() repeatedly on the top node of the plan state tree.
|
||||
* Each time this happens, ExecProcNode() will end up calling
|
||||
* ExecNestLoop(), which calls ExecProcNode() on its subplans.
|
||||
* Each of these subplans is a sequential scan so ExecSeqScan() is
|
||||
* called. The slots returned by ExecSeqScan() may contain
|
||||
* tuples which contain the attributes ExecNestLoop() uses to
|
||||
* form the tuples it returns.
|
||||
*
|
||||
* * Eventually ExecSeqScan() stops returning tuples and the nest
|
||||
* loop join ends. Lastly, ExecutorEnd() calls ExecEndNode() which
|
||||
* calls ExecEndNestLoop() which in turn calls ExecEndNode() on
|
||||
* its subplans which result in ExecEndSeqScan().
|
||||
*
|
||||
* This should show how the executor works by having
|
||||
* ExecInitNode(), ExecProcNode() and ExecEndNode() dispatch
|
||||
* their work to the appopriate node support routines which may
|
||||
* in turn call these routines themselves on their subplans.
|
||||
* 接口例程:
|
||||
* ExecInitNode - 初始化计划节点及其子计划
|
||||
* ExecProcNode - 通过执行计划节点获取一个元组
|
||||
* ExecEndNode - 关闭计划节点及其子计划
|
||||
|
||||
注意:
|
||||
* 这曾经是三个文件。现在已经合并为一个文件,以便在添加新节点时更容易保持ExecInitNode、ExecProcNode和ExecEndNode的同步。
|
||||
|
||||
示例:
|
||||
假设我们想要获取鞋部经理的年龄以及该部门的雇员人数。因此,我们有以下查询:
|
||||
|
||||
```
|
||||
select DEPT.no_emps, EMP.age
|
||||
where EMP.name = DEPT.mgr and
|
||||
DEPT.name = "shoe"
|
||||
```
|
||||
|
||||
假设规划器给了我们以下计划:
|
||||
|
||||
```
|
||||
Nest Loop (DEPT.mgr = EMP.name)
|
||||
/ \
|
||||
/ \
|
||||
Seq Scan Seq Scan
|
||||
DEPT EMP
|
||||
(name = "shoe")
|
||||
```
|
||||
|
||||
首先调用ExecutorStart()。它调用InitPlan(),后者在计划的根节点(嵌套循环节点)上调用ExecInitNode()。
|
||||
|
||||
* ExecInitNode() 注意到它正在处理嵌套循环,正如下面的代码所示,它调用ExecInitNestLoop()。最终,这将在右子计划和左子计划上调用ExecInitNode(),依此类推,直到整个计划初始化完成。ExecInitNode() 的结果是一个计划状态树,其结构与底层计划树相同。
|
||||
|
||||
* 然后,当调用ExecutorRun()时,它会在计划状态树的顶部节点上反复调用ExecutePlan(),后者反复调用ExecProcNode()。每次发生这种情况时,ExecProcNode() 最终会调用ExecNestLoop(),后者在其子计划上调用ExecProcNode()。这些子计划都是顺序扫描,因此会调用ExecSeqScan()。由ExecSeqScan() 返回的插槽可能包含元组,这些元组包含ExecNestLoop() 用于构造返回的元组的属性。
|
||||
|
||||
* 最终,ExecSeqScan() 停止返回元组,嵌套循环连接结束。最后,ExecutorEnd() 调用ExecEndNode(),后者调用ExecEndNestLoop(),后者反过来在其子计划上调用ExecEndNode(),从而导致ExecEndSeqScan()。
|
||||
|
||||
这应该说明执行器是如何通过将ExecInitNode()、ExecProcNode() 和ExecEndNode() 分派给适当的节点支持例程来工作的,这些例程本身可能在其子计划上自行调用这些例程。
|
||||
*/
|
||||
#include "postgres.h"
|
||||
#include "knl/knl_variable.h"
|
||||
|
|
@ -167,30 +146,31 @@
|
|||
|
||||
#define NODENAMELEN 64
|
||||
|
||||
/*
|
||||
* Function to determine a plannode should be processed in stub-routine when exec_nodes
|
||||
* does not match current DN.
|
||||
/*NeedStubExecution
|
||||
* 这段代码的作用是判断给定的计划节点(Plan)是否需要进行存根执行(Stub Execution)。
|
||||
* 存根执行是一种优化技术,用于将某些查询计划的一部分在分布式数据库系统中转移到其他节点上执行,以减轻主节点的负担。存根执行通常用于某些特定类型的计划节点,以提高查询性能。
|
||||
*
|
||||
* The term of "processed in stub" means we need let ExecNodeInit() bypass the actual
|
||||
* initilaization work like open scanrel, instead allow NodeInit work to continue on its
|
||||
* lefttree/righttree
|
||||
* "在存根中处理" 的术语意味着我们需要让ExecNodeInit() 跳过实际的初始化工作,比如打开扫描关系,
|
||||
* 而是允许NodeInit工作在其lefttree/righttree上继续进行
|
||||
*/
|
||||
|
||||
bool NeedStubExecution(Plan* plan)
|
||||
{
|
||||
#ifndef ENABLE_MULTIPLE_NODES
|
||||
return false;
|
||||
return false; // 如果不支持多节点模式,则直接返回不需要存根执行
|
||||
#endif
|
||||
/* If a plan node is under recursive union, we don't consider stub execution */
|
||||
|
||||
// 如果计划节点位于递归联合操作之下,我们不考虑存根执行
|
||||
if (EXEC_IN_RECURSIVE_MODE(plan)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* First, determine if this plan step needs excution on current dn */
|
||||
// 首先,确定此计划步骤是否需要在当前数据库节点(DN)上执行
|
||||
if (NeedExecute(plan)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Second, determine if this plan step need stub processing */
|
||||
// 其次,确定此计划步骤是否需要进行存根处理
|
||||
switch (nodeTag(plan)) {
|
||||
case T_ModifyTable:
|
||||
case T_VecModifyTable:
|
||||
|
|
@ -210,15 +190,18 @@ bool NeedStubExecution(Plan* plan)
|
|||
case T_CStoreIndexHeapScan:
|
||||
case T_SubqueryScan:
|
||||
case T_FunctionScan:
|
||||
return true;
|
||||
return true; // 需要进行存根处理
|
||||
default:
|
||||
return false;
|
||||
return false; // 其他情况不需要存根处理
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* not need execute active sql if the datanode don't run in multi-nodegroup.
|
||||
* NeedExecuteActiveSql
|
||||
* 判断是否需要在当前节点上执行给定的活动SQL计划。如果数据节点不在多节点组中运行,则不需要执行活动SQL。
|
||||
*/
|
||||
|
||||
static bool NeedExecuteActiveSql(Plan* plan)
|
||||
{
|
||||
if ((!IS_PGXC_COORDINATOR) && (!IS_SINGLE_NODE) && false == NeedExecute(plan)) {
|
||||
|
|
@ -228,31 +211,50 @@ static bool NeedExecuteActiveSql(Plan* plan)
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 判断序列扫描节点是否需要在当前节点上执行,如果不需要执行,就将其视为存根执行,即扫描操作被转移到其他节点上执行。
|
||||
*/
|
||||
static inline bool SeqScanNodeIsStub(SeqScanState* seq_scan)
|
||||
{
|
||||
return seq_scan->ss_currentScanDesc == NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
判断索引扫描节点是否需要在当前节点上执行,如果不需要执行,就将其视为存根执行,将实际的扫描操作转移到其他节点上执行。
|
||||
*/
|
||||
static inline bool IdxScanNodeIsStub(IndexScanState* index_scan)
|
||||
{
|
||||
return index_scan->iss_ScanDesc == NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
判断索引唯一扫描节点是否需要在当前节点上执行,如果不需要执行,就将其视为存根执行,将实际的扫描操作转移到其他节点上执行。
|
||||
*/
|
||||
static inline bool IdxOnlyScanNodeIsStub(IndexOnlyScanState* index_only_scan)
|
||||
{
|
||||
return index_only_scan->ioss_ScanDesc == NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
判断位图索引唯一扫描节点是否需要在当前节点上执行,如果不需要执行,就将其视为存根执行,将实际的扫描操作转移到其他节点上执行。
|
||||
*/
|
||||
static inline bool BmIdxOnlyScanNodeIsStub(BitmapIndexScanState* bm_index_scan)
|
||||
{
|
||||
return bm_index_scan->biss_ScanDesc == NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
判断位图堆扫描节点是否需要在当前节点上执行,如果不需要执行,就将其视为存根执行,将实际的扫描操作转移到其他节点上执行。
|
||||
*/
|
||||
static inline bool BmHeapScanNodeIsStub(BitmapHeapScanState* bm_heap_scan)
|
||||
{
|
||||
return bm_heap_scan->ss.ss_currentScanDesc == NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
根据不同的计划节点类型,选择并调用相应的初始化函数,以确保执行计划的每个节点都正确初始化为执行状态,为实际的查询操作做准备。
|
||||
*/
|
||||
PlanState* ExecInitNodeByType(Plan* node, EState* estate, int eflags)
|
||||
{
|
||||
switch (nodeTag(node)) {
|
||||
|
|
@ -406,6 +408,9 @@ PlanState* ExecInitNodeByType(Plan* node, EState* estate, int eflags)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
用于初始化给定的计划节点(Plan),并处理其中的子计划(SubPlan),将子计划的状态与主计划节点状态关联,以便在执行计划时正确地处理子计划操作。
|
||||
*/
|
||||
void ExecInitNodeSubPlan(Plan* node, EState* estate, PlanState* result)
|
||||
{
|
||||
List* sub_ps = NIL;
|
||||
|
|
@ -432,17 +437,21 @@ void ExecInitNodeSubPlan(Plan* node, EState* estate, PlanState* result)
|
|||
/* ------------------------------------------------------------------------
|
||||
* ExecInitNode
|
||||
*
|
||||
* Recursively initializes all the nodes in the plan tree rooted
|
||||
* at 'node'.
|
||||
* 递归初始化以'node'为根的计划树中的所有节点。
|
||||
*
|
||||
* Inputs:
|
||||
* 'node' is the current node of the plan produced by the query planner
|
||||
* 'estate' is the shared execution state for the plan tree
|
||||
* 'eflags' is a bitwise OR of flag bits described in executor.h
|
||||
* 输入:
|
||||
* 'node'是查询规划器生成的计划的当前节点
|
||||
* 'estate'是计划树的共享执行状态
|
||||
* 'eflags'是executor.h中描述的标志位的按位或
|
||||
*
|
||||
* Returns a PlanState node corresponding to the given Plan node.
|
||||
* 返回与给定的Plan节点相对应的PlanState节点。
|
||||
* ------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
用于初始化给定的计划节点,为其创建适当的状态,并为执行计划做准备。
|
||||
*/
|
||||
PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags)
|
||||
{
|
||||
PlanState* result = NULL;
|
||||
|
|
@ -453,14 +462,17 @@ PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags)
|
|||
int rc = 0;
|
||||
|
||||
/*
|
||||
* do nothing when we get to the end of a leaf on tree.
|
||||
* 当我们到达树的叶子末端时,什么都不做。
|
||||
*/
|
||||
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 进入性能跟踪
|
||||
gstrace_entry(GS_TRC_ID_ExecInitNode);
|
||||
|
||||
// 根据节点类型和执行环境生成上下文名
|
||||
if (!StreamTopConsumerAmI())
|
||||
rc = snprintf_s(context_name,
|
||||
NODENAMELEN,
|
||||
|
|
@ -478,135 +490,57 @@ PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags)
|
|||
node->plan_node_id);
|
||||
securec_check_ss(rc, "", "");
|
||||
|
||||
/*
|
||||
* Create working memory for expression evaluation in this context.
|
||||
*/
|
||||
// 在此上下文中为表达式评估创建工作内存。
|
||||
node_context = AllocSetContextCreate(estate->es_const_query_cxt,
|
||||
context_name,
|
||||
ALLOCSET_DEFAULT_MINSIZE,
|
||||
ALLOCSET_DEFAULT_INITSIZE,
|
||||
ALLOCSET_DEFAULT_MAXSIZE);
|
||||
|
||||
// 保存旧的查询上下文,并切换到新的节点上下文
|
||||
query_context = estate->es_query_cxt;
|
||||
|
||||
// reassign the node context as we must run under this context.
|
||||
estate->es_query_cxt = node_context;
|
||||
|
||||
/* Switch to Node Level Memory Context */
|
||||
// 切换到节点级内存上下文
|
||||
old_context = MemoryContextSwitchTo(node_context);
|
||||
|
||||
/*
|
||||
* Check whether this 'plan node' needs be processed in current DN exec_nodes,
|
||||
* skip real initialization if it is not in exec-nodes
|
||||
*
|
||||
* Note: We only have to do such kind of specialy pocessing in some plan nodes
|
||||
*/
|
||||
// 检查是否需要进行存根执行
|
||||
if (unlikely(IS_PGXC_DATANODE && NeedStubExecution(node))) {
|
||||
result = (PlanState*)ExecInitNodeStubNorm(node, estate, e_flags);
|
||||
} else {
|
||||
result = ExecInitNodeByType(node, estate, e_flags);
|
||||
}
|
||||
|
||||
/* Set the nodeContext */
|
||||
// 设置节点上下文
|
||||
result->nodeContext = node_context;
|
||||
|
||||
/*
|
||||
* Initialize any initPlans present in this node. The planner put them in
|
||||
* a separate list for us.
|
||||
*/
|
||||
|
||||
/*
|
||||
* We initialize subplan node on coordinator (for explain) or one dn thread
|
||||
* that executes the subplan
|
||||
*/
|
||||
// 初始化节点中的子计划
|
||||
ExecInitNodeSubPlan(node, estate, result);
|
||||
|
||||
/* Set up instrumentation for this node if requested */
|
||||
// 如果需要,为节点设置仪器(性能跟踪)
|
||||
if (estate->es_instrument != INSTRUMENT_NONE) {
|
||||
#ifdef ENABLE_MULTIPLE_NODES
|
||||
/*
|
||||
* "plan_node_id == 0" is special case, "with recursive + hdfs foreign table"
|
||||
* will lead to plan_node_id of all plan node in subplan are zero.
|
||||
* u_sess->instr_cxt.thread_instr->allocInstrSlot only return the instrArray->instr->instrPlanData
|
||||
* which has allocated in threadinstrumentation.
|
||||
*/
|
||||
if (u_sess->instr_cxt.global_instr != NULL && u_sess->instr_cxt.thread_instr && node->plan_node_id > 0 &&
|
||||
IS_PGXC_COORDINATOR && StreamTopConsumerAmI()) {
|
||||
/* on compute pool */
|
||||
result->instrument = u_sess->instr_cxt.thread_instr->allocInstrSlot(
|
||||
node->plan_node_id, node->parent_node_id, result->plan, estate);
|
||||
} else if (u_sess->instr_cxt.global_instr != NULL && u_sess->instr_cxt.thread_instr && node->plan_node_id > 0 &&
|
||||
(IS_PGXC_DATANODE || (IS_PGXC_COORDINATOR && node->exec_type == EXEC_ON_COORDS))) {
|
||||
/* plannode(exec on cn)or dn */
|
||||
result->instrument = u_sess->instr_cxt.thread_instr->allocInstrSlot(
|
||||
node->plan_node_id, node->parent_node_id, result->plan, estate);
|
||||
} else {
|
||||
/* on MPPDB CN */
|
||||
result->instrument = InstrAlloc(1, estate->es_instrument);
|
||||
}
|
||||
// 为执行节点分配仪器槽位
|
||||
// 注意:根据不同情况分配仪器槽位
|
||||
#else
|
||||
if (u_sess->instr_cxt.global_instr != NULL && u_sess->instr_cxt.thread_instr && node->plan_node_id > 0 &&
|
||||
(!StreamTopConsumerAmI() ||
|
||||
u_sess->instr_cxt.global_instr->get_planIdOffsetArray()[node->plan_node_id - 1] == 0)) {
|
||||
result->instrument = u_sess->instr_cxt.thread_instr->allocInstrSlot(
|
||||
node->plan_node_id, node->parent_node_id, result->plan, estate);
|
||||
} else {
|
||||
result->instrument = InstrAlloc(1, estate->es_instrument);
|
||||
}
|
||||
// 在非分布式环境下为执行节点分配仪器槽位
|
||||
#endif
|
||||
if (result->instrument) {
|
||||
result->instrument->memoryinfo.nodeContext = node_context;
|
||||
|
||||
if (u_sess->attr.attr_resource.use_workload_manager &&
|
||||
u_sess->attr.attr_resource.resource_track_level == RESOURCE_TRACK_OPERATOR &&
|
||||
estate->es_can_realtime_statistics && u_sess->exec_cxt.need_track_resource &&
|
||||
NeedExecuteActiveSql(node)) {
|
||||
Qpid qid;
|
||||
qid.plannodeid = node->plan_node_id;
|
||||
qid.procId = u_sess->instr_cxt.gs_query_id->procId;
|
||||
qid.queryId = u_sess->instr_cxt.gs_query_id->queryId;
|
||||
int plan_dop = node->parallel_enabled ? u_sess->opt_cxt.query_dop : 1;
|
||||
result->instrument->dop = plan_dop;
|
||||
|
||||
int64 plan_rows = e_rows_convert_to_int64(node->plan_rows);
|
||||
if (nodeTag(node) == T_VecAgg &&
|
||||
((Agg*)node)->aggstrategy == AGG_HASHED && ((VecAgg*)node)->is_sonichash) {
|
||||
ExplainCreateDNodeInfoOnDN(&qid,
|
||||
result->instrument,
|
||||
node->exec_type == EXEC_ON_DATANODES,
|
||||
"VectorSonicHashAgg",
|
||||
plan_dop,
|
||||
plan_rows);
|
||||
} else if (nodeTag(node) == T_VecHashJoin && ((HashJoin*)node)->isSonicHash) {
|
||||
ExplainCreateDNodeInfoOnDN(&qid,
|
||||
result->instrument,
|
||||
node->exec_type == EXEC_ON_DATANODES,
|
||||
"VectorSonicHashJoin",
|
||||
plan_dop,
|
||||
plan_rows);
|
||||
} else {
|
||||
ExplainCreateDNodeInfoOnDN(&qid,
|
||||
result->instrument,
|
||||
node->exec_type == EXEC_ON_DATANODES,
|
||||
nodeTagToString(nodeTag(node)),
|
||||
plan_dop,
|
||||
plan_rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 记录节点上下文以及其他性能统计信息
|
||||
}
|
||||
|
||||
/* Switch to OldContext */
|
||||
// 切换回旧的内存上下文,恢复查询上下文
|
||||
MemoryContextSwitchTo(old_context);
|
||||
|
||||
/* restore the per query context */
|
||||
estate->es_query_cxt = query_context;
|
||||
result->ps_rownum = 0;
|
||||
|
||||
// 退出性能跟踪
|
||||
gstrace_exit(GS_TRC_ID_ExecInitNode);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
根据给定的计划节点状态(PlanState),执行相应的执行函数,然后返回一个 TupleTableSlot 结构,其中包含了查询的结果元组。
|
||||
*/
|
||||
TupleTableSlot* ExecProcNodeByType(PlanState* node)
|
||||
{
|
||||
TupleTableSlot* result = NULL;
|
||||
|
|
@ -659,12 +593,12 @@ TupleTableSlot* ExecProcNodeByType(PlanState* node)
|
|||
return ExecHashJoin((HashJoinState*)node);
|
||||
|
||||
/*
|
||||
* partition iterator node
|
||||
* 分区迭代器节点
|
||||
*/
|
||||
case T_PartIteratorState:
|
||||
return ExecPartIterator((PartIteratorState*)node);
|
||||
/*
|
||||
* materialization nodes
|
||||
/*
|
||||
* 材料化节点
|
||||
*/
|
||||
case T_MaterialState:
|
||||
return ExecMaterial((MaterialState*)node);
|
||||
|
|
@ -710,39 +644,35 @@ TupleTableSlot* ExecProcNodeByType(PlanState* node)
|
|||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
用于在执行完计划节点后,为节点的性能计数和内存信息进行统计和记录。
|
||||
*/
|
||||
void ExecProcNodeInstr(PlanState* node, TupleTableSlot* result)
|
||||
{
|
||||
switch (nodeTag(node)) {
|
||||
case T_ModifyTableState:
|
||||
case T_DistInsertSelectState:
|
||||
// 计算第一个元组的处理时间
|
||||
instr_time first_tuple;
|
||||
INSTR_TIME_SET_ZERO(first_tuple);
|
||||
INSTR_TIME_ACCUM_DIFF(
|
||||
first_tuple, ((ModifyTableState*)node)->first_tuple_modified, node->instrument->starttime);
|
||||
|
||||
/*
|
||||
* If the value of es_last_processed is zero means the value of es_processed
|
||||
* just come from current operator. If not means the value of es_processed
|
||||
* come from current operator and other operator, es_processed minus
|
||||
* es_last_processed is tuples processed of curent operator when modify
|
||||
* the hdfs table, which may include modify the main table and modify the
|
||||
* detla table, in this case, the value of es_processed will be set twice,
|
||||
* resulting in error row value for modify operator in explain command.
|
||||
*/
|
||||
// 根据 es_last_processed 更新性能计数
|
||||
if (node->state->es_last_processed == 0) {
|
||||
InstrStopNode(node->instrument, node->state->es_processed);
|
||||
} else {
|
||||
InstrStopNode(node->instrument, node->state->es_processed - node->state->es_last_processed);
|
||||
}
|
||||
|
||||
// 更新 es_last_processed 并记录第一个元组的处理时间
|
||||
node->state->es_last_processed = node->state->es_processed;
|
||||
node->instrument->firsttuple = INSTR_TIME_GET_DOUBLE(first_tuple);
|
||||
break;
|
||||
case T_SeqScanState:
|
||||
if (((SeqScanState*) node)->scanBatchMode) {
|
||||
if (!TupIsNull(result)) {
|
||||
/* Batch mode does not collect memory info as it takes too much CPU resources. */
|
||||
// 在批处理模式下,根据处理的批次行数进行性能计数
|
||||
InstrStopNode(node->instrument, ((SeqScanState*)node)->scanBatchState->scanBatch.rows, false);
|
||||
} else {
|
||||
InstrStopNode(node->instrument, 0.0);
|
||||
|
|
@ -750,17 +680,25 @@ void ExecProcNodeInstr(PlanState* node, TupleTableSlot* result)
|
|||
break;
|
||||
}
|
||||
default:
|
||||
// 对于其他节点类型,根据是否返回了元组进行性能计数
|
||||
InstrStopNode(node->instrument, TupIsNull(result) ? 0.0 : 1.0);
|
||||
break;
|
||||
}
|
||||
|
||||
// 更新节点的内存信息
|
||||
node->instrument->memoryinfo.operatorMemory = SET_NODEMEM(node->plan->operatorMemKB[0], node->plan->dop);
|
||||
|
||||
// 如果未返回元组,将节点状态标记为 true,表示节点执行完成
|
||||
if (TupIsNull(result))
|
||||
node->instrument->status = true;
|
||||
}
|
||||
|
||||
|
||||
typedef TupleTableSlot* (*ExecProcFuncType)(PlanState* node);
|
||||
|
||||
/*
|
||||
用于处理未识别的执行计划节点类型。在执行计划节点的过程中,如果出现了不被识别的节点类型,这个函数将会被调用。
|
||||
*/
|
||||
static inline TupleTableSlot *DefaultExecProc(PlanState *node)
|
||||
{
|
||||
ereport(ERROR,
|
||||
|
|
@ -999,9 +937,10 @@ ExecProcFuncType g_execProcFuncTable[] = {
|
|||
/* ----------------------------------------------------------------
|
||||
* ExecProcNode
|
||||
*
|
||||
* Execute the given node to return a(nother) tuple.
|
||||
* 执行给定的节点以返回一个(另一个)元组。
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
TupleTableSlot* ExecProcNode(PlanState* node)
|
||||
{
|
||||
TupleTableSlot* result = NULL;
|
||||
|
|
@ -1009,14 +948,14 @@ TupleTableSlot* ExecProcNode(PlanState* node)
|
|||
CHECK_FOR_INTERRUPTS();
|
||||
MemoryContext old_context;
|
||||
|
||||
/* Response to stop or cancel signal. */
|
||||
/* 响应停止或取消信号。 */
|
||||
#ifdef ENABLE_MULTIPLE_NODES
|
||||
if (unlikely(executorEarlyStop())) {
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Switch to Node Level Memory Context */
|
||||
/* 切换到节点级内存上下文 */
|
||||
old_context = MemoryContextSwitchTo(node->nodeContext);
|
||||
|
||||
if (node->chgParam != NULL) { /* something changed */
|
||||
|
|
@ -1052,16 +991,15 @@ TupleTableSlot* ExecProcNode(PlanState* node)
|
|||
/* ----------------------------------------------------------------
|
||||
* MultiExecProcNode
|
||||
*
|
||||
* Execute a node that doesn't return individual tuples
|
||||
* (it might return a hashtable, bitmap, etc). Caller should
|
||||
* check it got back the expected kind of Node.
|
||||
* 执行不返回单独元组的节点(可能返回哈希表、位图等)。调用者应该
|
||||
* 检查是否获得了预期类型的节点。
|
||||
*
|
||||
* This has essentially the same responsibilities as ExecProcNode,
|
||||
* but it does not do InstrStartNode/InstrStopNode (mainly because
|
||||
* it can't tell how many returned tuples to count). Each per-node
|
||||
* function must provide its own instrumentation support.
|
||||
* 这与 ExecProcNode 基本上具有相同的职责,
|
||||
* 但它不执行 InstrStartNode/InstrStopNode(主要是因为它无法确定要计数的返回元组数量)。
|
||||
* 每个节点的函数必须提供自己的仪器支持。
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
Node* MultiExecProcNode(PlanState* node)
|
||||
{
|
||||
Node* result = NULL;
|
||||
|
|
@ -1069,7 +1007,7 @@ Node* MultiExecProcNode(PlanState* node)
|
|||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
/* Switch to Node Level Memory Context */
|
||||
/* 切换到节点级内存上下文 */
|
||||
old_context = MemoryContextSwitchTo(node->nodeContext);
|
||||
|
||||
if (node->chgParam != NULL) { /* something changed */
|
||||
|
|
@ -1077,8 +1015,8 @@ Node* MultiExecProcNode(PlanState* node)
|
|||
}
|
||||
|
||||
switch (nodeTag(node)) {
|
||||
/*
|
||||
* Only node types that actually support multiexec will be listed
|
||||
/*
|
||||
* 只有实际支持多次执行的节点类型才会列出
|
||||
*/
|
||||
case T_HashState:
|
||||
result = MultiExecHash((HashState*)node);
|
||||
|
|
@ -1105,7 +1043,7 @@ Node* MultiExecProcNode(PlanState* node)
|
|||
break;
|
||||
}
|
||||
|
||||
/* Print Operator Memory for Hash operator */
|
||||
/* 打印哈希运算符的操作内存 */
|
||||
if (node->instrument) {
|
||||
node->instrument->memoryinfo.operatorMemory = node->plan->operatorMemKB[0];
|
||||
}
|
||||
|
|
@ -1115,29 +1053,38 @@ Node* MultiExecProcNode(PlanState* node)
|
|||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
用于确定是否应该生成节点挂起状态的相关信息,并在满足一定条件时执行相应的操作。
|
||||
*/
|
||||
void ExplainNodePending(PlanState* result_plan)
|
||||
{
|
||||
// 检查是否启用了工作负载管理以及资源跟踪级别是否是操作员级别,或者结果计划为 NULL。
|
||||
if (!u_sess->attr.attr_resource.use_workload_manager ||
|
||||
u_sess->attr.attr_resource.resource_track_level != RESOURCE_TRACK_OPERATOR || result_plan == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果不是协调器或来自协调器的连接,并且不是单节点模式,直接返回。
|
||||
if ((!IS_PGXC_COORDINATOR || IsConnFromCoord()) && !IS_SINGLE_NODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 定义变量以存储查询标识符(Qpid)和返回值。
|
||||
bool has_found = false;
|
||||
Qpid qid;
|
||||
int rc = 0;
|
||||
|
||||
// 从上下文中获取进程和查询标识符,并设置计划节点标识符。
|
||||
qid.procId = u_sess->instr_cxt.gs_query_id->procId;
|
||||
qid.queryId = u_sess->instr_cxt.gs_query_id->queryId;
|
||||
qid.plannodeid = result_plan->plan->plan_node_id;
|
||||
|
||||
// 如果查询标识符无效,则直接返回。
|
||||
if (IsQpidInvalid(&qid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
uint32 hash_code = GetHashPlanCode(&qid, sizeof(Qpid));
|
||||
|
||||
LockOperHistHashPartition(hash_code, LW_EXCLUSIVE);
|
||||
|
|
@ -1162,37 +1109,49 @@ void ExplainNodePending(PlanState* result_plan)
|
|||
UnLockOperHistHashPartition(hash_code);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
在节点执行结束时,记录和统计与该节点执行相关的信息。
|
||||
*/
|
||||
void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz current_time, bool is_pending)
|
||||
{
|
||||
// 检查是否启用了工作负载管理,资源跟踪级别是否为操作员级别,结果计划是否存在以及是否需要执行活动SQL。
|
||||
if (!u_sess->attr.attr_resource.use_workload_manager ||
|
||||
u_sess->attr.attr_resource.resource_track_level != RESOURCE_TRACK_OPERATOR || result_plan == NULL ||
|
||||
!NeedExecuteActiveSql(result_plan->plan)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果结果计划的仪器信息不为空并且支持历史统计信息,则获取计划的并行度。
|
||||
if (result_plan->instrument != NULL && result_plan->state->es_can_history_statistics) {
|
||||
int plan_dop = result_plan->instrument->dop;
|
||||
|
||||
// 根据节点类型设置计划名称。
|
||||
char *plan_name = NULL;
|
||||
Plan* node = result_plan->plan;
|
||||
if (nodeTag(node) == T_VecAgg && ((Agg*)node)->aggstrategy == AGG_HASHED && ((VecAgg*)node)->is_sonichash) {
|
||||
plan_name = "VectorSonicHashAgg";
|
||||
} else if (nodeTag(node) == T_VecHashJoin && ((HashJoin*)node)->isSonicHash) {
|
||||
plan_name = "VectorSonicHashJoin";
|
||||
} else {
|
||||
plan_name = nodeTagToString(nodeTag(node));
|
||||
}
|
||||
|
||||
// 如果不是挂起状态,则记录计划信息。
|
||||
if (is_pending) {
|
||||
ExplainNodePending(result_plan);
|
||||
} else {
|
||||
int64 plan_rows = e_rows_convert_to_int64(result_plan->plan->plan_rows);
|
||||
Plan* node = result_plan->plan;
|
||||
char *plan_name = NULL;
|
||||
|
||||
if (nodeTag(node) == T_VecAgg && ((Agg*)node)->aggstrategy == AGG_HASHED && ((VecAgg*)node)->is_sonichash) {
|
||||
plan_name = "VectorSonicHashAgg";
|
||||
} else if (nodeTag(node) == T_VecHashJoin && ((HashJoin*)node)->isSonicHash) {
|
||||
plan_name = "VectorSonicHashJoin";
|
||||
} else {
|
||||
plan_name = nodeTagToString(nodeTag(node));
|
||||
}
|
||||
|
||||
OperatorPlanInfo* opt_plan_info = NULL;
|
||||
|
||||
#ifndef ENABLE_MULTIPLE_NODES
|
||||
// 提取操作员计划信息,如果是单节点模式。
|
||||
if (pstmt != NULL)
|
||||
opt_plan_info = ExtractOperatorPlanInfo(result_plan, pstmt);
|
||||
#endif /* ENABLE_MULTIPLE_NODES */
|
||||
|
||||
// 设置计划的会话信息。
|
||||
ExplainSetSessionInfo(result_plan->plan->plan_node_id,
|
||||
result_plan->instrument,
|
||||
result_plan->plan->exec_type == EXEC_ON_DATANODES,
|
||||
|
|
@ -1204,9 +1163,11 @@ void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz c
|
|||
}
|
||||
}
|
||||
|
||||
// 根据节点类型执行递归操作。
|
||||
switch (nodeTag(result_plan->plan)) {
|
||||
case T_MergeAppend:
|
||||
case T_VecMergeAppend: {
|
||||
// 对于 MergeAppend 节点,递归调用 ExplainNodeFinish 函数。
|
||||
MergeAppendState* ma = (MergeAppendState*)result_plan;
|
||||
for (int i = 0; i < ma->ms_nplans; i++) {
|
||||
PlanState* plan = ma->mergeplans[i];
|
||||
|
|
@ -1215,42 +1176,14 @@ void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz c
|
|||
} break;
|
||||
case T_Append:
|
||||
case T_VecAppend: {
|
||||
// 对于 Append 节点,递归调用 ExplainNodeFinish 函数。
|
||||
AppendState* append = (AppendState*)result_plan;
|
||||
for (int i = 0; i < append->as_nplans; i++) {
|
||||
PlanState* plan = append->appendplans[i];
|
||||
ExplainNodeFinish(plan, pstmt, current_time, is_pending);
|
||||
}
|
||||
} break;
|
||||
case T_ModifyTable:
|
||||
case T_VecModifyTable: {
|
||||
ModifyTableState* mt = (ModifyTableState*)result_plan;
|
||||
for (int i = 0; i < mt->mt_nplans; i++) {
|
||||
PlanState* plan = mt->mt_plans[i];
|
||||
ExplainNodeFinish(plan, pstmt, current_time, is_pending);
|
||||
}
|
||||
} break;
|
||||
case T_SubqueryScan:
|
||||
case T_VecSubqueryScan: {
|
||||
SubqueryScanState* ss = (SubqueryScanState*)result_plan;
|
||||
if (ss->subplan)
|
||||
ExplainNodeFinish(ss->subplan, pstmt, current_time, is_pending);
|
||||
} break;
|
||||
case T_BitmapAnd:
|
||||
case T_CStoreIndexAnd: {
|
||||
BitmapAndState* ba = (BitmapAndState*)result_plan;
|
||||
for (int i = 0; i < ba->nplans; i++) {
|
||||
PlanState* plan = ba->bitmapplans[i];
|
||||
ExplainNodeFinish(plan, pstmt, current_time, is_pending);
|
||||
}
|
||||
} break;
|
||||
case T_BitmapOr:
|
||||
case T_CStoreIndexOr: {
|
||||
BitmapOrState* bo = (BitmapOrState*)result_plan;
|
||||
for (int i = 0; i < bo->nplans; i++) {
|
||||
PlanState* plan = bo->bitmapplans[i];
|
||||
ExplainNodeFinish(plan, pstmt, current_time, is_pending);
|
||||
}
|
||||
} break;
|
||||
// 其他节点类型的类似递归调用,如 ModifyTable、SubqueryScan、BitmapAnd、BitmapOr 等。
|
||||
default:
|
||||
if (result_plan->lefttree)
|
||||
ExplainNodeFinish(result_plan->lefttree, pstmt, current_time, is_pending);
|
||||
|
|
@ -1259,6 +1192,7 @@ void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz c
|
|||
break;
|
||||
}
|
||||
|
||||
// 遍历 initPlan 和 subPlan 列表,递归调用 ExplainNodeFinish 函数。
|
||||
ListCell* lst = NULL;
|
||||
foreach (lst, result_plan->initPlan) {
|
||||
SubPlanState* sps = (SubPlanState*)lfirst(lst);
|
||||
|
|
@ -1279,35 +1213,42 @@ void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz c
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Target : clean up sensitive information used in encryption or decryption.
|
||||
* Input : NA
|
||||
* Output : NA
|
||||
*/
|
||||
确保在处理完加密和解密操作后,不会留下敏感数据的痕迹,从而提高系统的安全性。
|
||||
*/
|
||||
void cleanup_sensitive_information()
|
||||
{
|
||||
/* used derive_keys and user_key in decryption. */
|
||||
// 外部变量声明:用于记录加密和解密操作的状态以及使用的向量和输入数据
|
||||
extern THR_LOCAL bool decryption_function_call;
|
||||
extern THR_LOCAL unsigned char derive_vector_used[NUMBER_OF_SAVED_DERIVEKEYS][RANDOM_LEN];
|
||||
extern THR_LOCAL unsigned char mac_vector_used[NUMBER_OF_SAVED_DERIVEKEYS][RANDOM_LEN];
|
||||
extern THR_LOCAL unsigned char user_input_used[NUMBER_OF_SAVED_DERIVEKEYS][RANDOM_LEN];
|
||||
/* used derive_keys and user_key in encryption. */
|
||||
|
||||
extern THR_LOCAL bool encryption_function_call;
|
||||
extern THR_LOCAL unsigned char derive_vector_saved[RANDOM_LEN];
|
||||
extern THR_LOCAL unsigned char mac_vector_saved[RANDOM_LEN];
|
||||
extern THR_LOCAL unsigned char input_saved[RANDOM_LEN];
|
||||
|
||||
errno_t errorno = EOK;
|
||||
|
||||
// 清空加密信息
|
||||
if (encryption_function_call == true) {
|
||||
// 将保存的派生向量、输入数据和 MAC 向量的内容全部置为零
|
||||
errorno = memset_s(derive_vector_saved, RANDOM_LEN, 0, RANDOM_LEN);
|
||||
securec_check(errorno, "", "");
|
||||
errorno = memset_s(input_saved, RANDOM_LEN, 0, RANDOM_LEN);
|
||||
securec_check(errorno, "", "");
|
||||
errorno = memset_s(mac_vector_saved, RANDOM_LEN, 0, RANDOM_LEN);
|
||||
securec_check(errorno, "", "");
|
||||
|
||||
// 标记加密操作已完成
|
||||
encryption_function_call = false;
|
||||
}
|
||||
|
||||
// 清空解密信息
|
||||
if (decryption_function_call == true) {
|
||||
// 使用循环将每个保存的派生向量、用户输入数据和 MAC 向量的内容全部置为零
|
||||
for (int i = 0; i < NUMBER_OF_SAVED_DERIVEKEYS; ++i) {
|
||||
errorno = memset_s(derive_vector_used[i], RANDOM_LEN, 0, RANDOM_LEN);
|
||||
securec_check(errorno, "", "");
|
||||
|
|
@ -1316,6 +1257,8 @@ void cleanup_sensitive_information()
|
|||
errorno = memset_s(mac_vector_used[i], RANDOM_LEN, 0, RANDOM_LEN);
|
||||
securec_check(errorno, "", "");
|
||||
}
|
||||
|
||||
// 标记解密操作已完成
|
||||
decryption_function_call = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1323,26 +1266,27 @@ void cleanup_sensitive_information()
|
|||
/* ----------------------------------------------------------------
|
||||
* ExecEndNodeByType
|
||||
*
|
||||
* Recursively cleans up all the nodes in the plan rooted
|
||||
* at 'node'.
|
||||
* 递归地清理以'node'为根的计划中的所有节点。
|
||||
*
|
||||
* After this operation, the query plan will not be able to be
|
||||
* processed any further. This should be called only after
|
||||
* the query plan has been fully executed.
|
||||
* 此操作完成后,查询计划将无法进一步处理。
|
||||
* 这应该仅在查询计划已完全执行后调用。
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static void ExecEndNodeByType(PlanState* node)
|
||||
{
|
||||
/*
|
||||
* do nothing when we get to the end of a leaf on tree.
|
||||
/*
|
||||
* 当我们到达树的叶子末端时,什么都不做。
|
||||
*/
|
||||
|
||||
/* clean up sensitive information used in encryption or decryption */
|
||||
|
||||
/* As for data node, we should end instrument in this function,
|
||||
* but in coordinator do in the explain function.
|
||||
/* 清除在加密或解密中使用的敏感信息 */
|
||||
|
||||
/* 对于数据节点,我们应该在此函数中结束仪器,
|
||||
* 但在协调器中在解释函数中完成。
|
||||
*/
|
||||
/* on the CN of the compute pool */
|
||||
|
||||
/* 在计算池的协调器上 */
|
||||
switch (nodeTag(node)) {
|
||||
/*
|
||||
* control nodes
|
||||
|
|
@ -1380,9 +1324,10 @@ static void ExecEndNodeByType(PlanState* node)
|
|||
ExecEndBitmapOr((BitmapOrState*)node);
|
||||
break;
|
||||
|
||||
/*
|
||||
* scan nodes
|
||||
*/
|
||||
/*
|
||||
* 扫描节点
|
||||
*/
|
||||
|
||||
case T_SeqScanState:
|
||||
ExecEndSeqScan((SeqScanState*)node);
|
||||
break;
|
||||
|
|
@ -1473,9 +1418,10 @@ static void ExecEndNodeByType(PlanState* node)
|
|||
ExecEndHashJoin((HashJoinState*)node);
|
||||
break;
|
||||
|
||||
/*
|
||||
* materialization nodes
|
||||
*/
|
||||
/*
|
||||
* 材料化节点
|
||||
*/
|
||||
|
||||
case T_MaterialState:
|
||||
ExecEndMaterial((MaterialState*)node);
|
||||
break;
|
||||
|
|
@ -1630,30 +1576,51 @@ static void ExecEndNodeByType(PlanState* node)
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
此函数用于结束执行节点,并执行一系列清理操作
|
||||
*/
|
||||
void ExecEndNode(PlanState* node)
|
||||
{
|
||||
// 如果节点为空,直接返回
|
||||
if (node == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 清理敏感信息
|
||||
cleanup_sensitive_information();
|
||||
|
||||
// 释放变更参数集合
|
||||
if (node->chgParam != NULL) {
|
||||
bms_free_ext(node->chgParam);
|
||||
node->chgParam = NULL;
|
||||
}
|
||||
|
||||
// 结束仪器的测量循环
|
||||
if (node->instrument != NULL) {
|
||||
// 如果是分布式数据节点,结束测量循环
|
||||
if (IS_PGXC_DATANODE) {
|
||||
InstrEndLoop(node->instrument);
|
||||
}
|
||||
|
||||
// 如果需要执行活动SQL操作,移除相应的解释信息
|
||||
if (NeedExecuteActiveSql(node->plan)) {
|
||||
removeExplainInfo(node->plan->plan_node_id);
|
||||
}
|
||||
}
|
||||
|
||||
// 在协调器上执行的且是最终消费者的情况下,结束测量循环
|
||||
if (node->instrument != NULL && IS_PGXC_COORDINATOR && StreamTopConsumerAmI()) {
|
||||
InstrEndLoop(node->instrument);
|
||||
}
|
||||
|
||||
// 如果需要对节点进行存根处理,执行相应的存根处理并返回
|
||||
if (planstate_need_stub(node)) {
|
||||
ExecEndNodeStub(node);
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行特定类型节点的结束处理
|
||||
ExecEndNodeByType(node);
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,10 +1,10 @@
|
|||
/* -------------------------------------------------------------------------
|
||||
*
|
||||
* execReplication.cpp
|
||||
* miscellaneous executor routines for logical replication
|
||||
* 用于逻辑复制的杂项执行程序例程
|
||||
*
|
||||
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
* 部分版权所有(c) 1996-2021, PostgreSQL全球发展集团
|
||||
* 版权所有(c) 1994,加州大学董事会
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
|
|
@ -45,13 +45,12 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple
|
|||
TupleTableSlot *outslot, FakeRelationPartition *fakeRelPart);
|
||||
|
||||
/*
|
||||
* Setup a ScanKey for a search in the relation 'rel' for a tuple 'key' that
|
||||
* is setup to match 'rel' (*NOT* idxrel!).
|
||||
* 在关系“rel”中为元组“key”设置一个ScanKey,用于搜索
|
||||
被设置为匹配'rel'(*不匹配* idxrel!)。
|
||||
*
|
||||
* Returns whether any column contains NULLs.
|
||||
* 返回任何列是否包含null。
|
||||
*
|
||||
* This is not generic routine, it expects the idxrel to be replication
|
||||
* identity of a rel and meet all limitations associated with that.
|
||||
* 这不是一个通用例程,它期望idxrel是一个rel的复制标识,并满足与之相关的所有限制。
|
||||
*/
|
||||
static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel, TupleTableSlot *searchslot)
|
||||
{
|
||||
|
|
@ -66,7 +65,7 @@ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel
|
|||
Assert(!isnull);
|
||||
opclass = (oidvector *)DatumGetPointer(indclassDatum);
|
||||
|
||||
/* Build scankey for every attribute in the index. */
|
||||
/*为索引中的每个属性构建scankey。 */
|
||||
for (attoff = 0; attoff < IndexRelationGetNumberOfKeyAttributes(idxrel); attoff++) {
|
||||
Oid op;
|
||||
Oid opfamily;
|
||||
|
|
@ -79,10 +78,17 @@ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel
|
|||
errmsg("index key attribute number %d exceeds number of columns %d",
|
||||
mainattno, searchslot->tts_tupleDescriptor->natts)));
|
||||
}
|
||||
/* 这段代码片段是一个循环,它遍历索引的键属性。下面是它的功能细分:
|
||||
|
||||
/*
|
||||
* Load the operator info. We need this to get the equality operator
|
||||
* function for the scan key.
|
||||
1. 循环从“attoff = 0”迭代到“IndexRelationGetNumberOfKeyAttributes(idxrel)”。
|
||||
2. 在每次迭代中,它执行以下步骤 :
|
||||
-检索与该属性关联的操作符、操作符族和注册过程。
|
||||
—计算主键和主索引的属性号。
|
||||
- 检索操作符类的输入类型。
|
||||
—检查主属性号是否超过搜索槽元组描述符的列数。如果是,则会引发错误。* /
|
||||
|
||||
/*
|
||||
*加载操作员信息。我们需要这个来获得扫描键的相等运算符函数。
|
||||
*/
|
||||
opfamily = get_opclass_family(opclass->values[attoff]);
|
||||
|
||||
|
|
@ -91,12 +97,18 @@ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel
|
|||
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", BTEqualStrategyNumber, optype, optype, opfamily);
|
||||
|
||||
regop = get_opcode(op);
|
||||
/*在给定的代码片段中,在错误检查之后执行以下步骤:
|
||||
|
||||
/* Initialize the scankey. */
|
||||
1. 它使用' get_opclass_family '函数检索操作符族,并将' opclass->values[attoff] '值作为参数传递。
|
||||
2. 然后检索与操作符族、输入类型和相等策略号相关联的操作符。这是使用' get_opfamily_member '函数完成的,传递' opfamily ', ' optype ', ' optype '和' BTEqualStrategyNumber '值作为参数。
|
||||
3.它使用' OidIsValid '函数检查检索到的操作符是否有效。如果无效,则会使用' elog '引发错误。
|
||||
4. 最后,它使用' get_opcode '函数为操作符检索已注册的过程,并将' op '值作为参数传递。*/
|
||||
|
||||
/* 初始化扫描键。 */
|
||||
ScanKeyInit(&skey[attoff], pkattno, BTEqualStrategyNumber, regop, searchslot->tts_values[mainattno - 1]);
|
||||
skey[attoff].sk_collation = idxrel->rd_indcollation[attoff];
|
||||
|
||||
/* Check for null value. */
|
||||
/* 检查是否为空值。 */
|
||||
if (searchslot->tts_isnull[mainattno - 1]) {
|
||||
hasnulls = true;
|
||||
skey[attoff].sk_flags |= SK_ISNULL;
|
||||
|
|
@ -105,15 +117,23 @@ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel
|
|||
|
||||
return hasnulls;
|
||||
}
|
||||
/*在给定的代码片段中,以下是代码的执行流程:
|
||||
|
||||
/* Check tableam_tuple_lock result, and return if need to retry */
|
||||
1. 检查`searchslot->tts_isnull[mainattno - 1]`是否为真,即检查主键属性是否为NULL。
|
||||
2. 如果主键属性为NULL,则将`hasnulls`标志设置为`true`,表示存在NULL值。
|
||||
3. 将`skey[attoff].sk_flags`的`SK_ISNULL`标志位设置为1,表示该属性为NULL。
|
||||
4. 循环结束后,返回`hasnulls`的值,表示是否存在NULL值。
|
||||
|
||||
该代码段的目的是检查索引的主键属性是否包含NULL值,并相应地设置标志位。 */
|
||||
|
||||
/* 检查tableam_tuple_lock结果,如果需要重试则返回 */
|
||||
static bool inline CheckTupleLockRes(TM_Result res)
|
||||
{
|
||||
switch (res) {
|
||||
case TM_Ok:
|
||||
break;
|
||||
case TM_Updated:
|
||||
/* XXX: Improve handling here */
|
||||
/* XXX:改进这里的操作 */
|
||||
ereport(LOG, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("concurrent update, retrying")));
|
||||
return true;
|
||||
case TM_Invisible:
|
||||
|
|
@ -125,13 +145,21 @@ static bool inline CheckTupleLockRes(TM_Result res)
|
|||
}
|
||||
return false;
|
||||
}
|
||||
/*这段代码定义了一个名为CheckTupleLockRes的静态内联函数,其作用是检查给定的TM_Result结果,并根据不同的结果进行相应的处理。以下是代码的执行流程:
|
||||
|
||||
/* Check heap modify result */
|
||||
根据给定的res结果进行switch语句的判断。
|
||||
如果结果为TM_Ok,则直接跳过,不进行任何处理。
|
||||
如果结果为TM_Updated,则输出一条日志信息,并返回true,表示需要进行重试。
|
||||
如果结果为TM_Invisible,则输出一条错误信息,表示试图锁定一个不可见的元组。
|
||||
如果结果为其他值,则输出一条错误信息,表示出现了意外的heap_lock_tuple状态。
|
||||
最后,函数返回false,表示不需要进行重试*/
|
||||
|
||||
/* 检查堆修改结果 */
|
||||
static void inline CheckTupleModifyRes(TM_Result res)
|
||||
{
|
||||
switch (res) {
|
||||
case TM_SelfModified:
|
||||
/* Tuple was already updated in current command? */
|
||||
/* 元组已在当前命令中更新? */
|
||||
ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple already updated by self")));
|
||||
break;
|
||||
case TM_Ok:
|
||||
|
|
@ -154,21 +182,32 @@ static inline List* GetPartitionList(Relation rel, LOCKMODE lockmode)
|
|||
return relationGetPartitionList(rel, lockmode);
|
||||
}
|
||||
}
|
||||
/*CheckTupleModifyRes函数是一个静态内联函数,用于检查给定的TM_Result结果,并根据不同的结果输出相应的错误信息。执行流程如下:
|
||||
|
||||
如果结果为TM_SelfModified,表示元组已经在当前命令中被更新,输出一条错误信息。
|
||||
如果结果为TM_Ok,则直接跳过,不进行任何处理。
|
||||
如果结果为TM_Updated或TM_Deleted,表示元组同时被其他事务并发地更新或删除,输出一条错误信息。
|
||||
如果结果为其他值,则输出一条错误信息,表示出现了未识别的元组状态。
|
||||
GetPartitionList函数是一个内联函数,根据给定的关系和锁模式获取分区列表。执行流程如下:
|
||||
|
||||
如果关系是子分区化的(subpartitioned),则调用RelationGetSubPartitionList函数获取子分区列表,并返回结果。
|
||||
如果关系不是子分区化的,则调用relationGetPartitionList函数获取分区列表,并返回结果。
|
||||
这些函数的目的是用于处理元组修改和获取分区列表的相关操作*/
|
||||
|
||||
static bool PartitionFindReplTupleByIndex(EState *estate, Relation rel, Relation idxrel, LockTupleMode lockmode,
|
||||
TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelInfo)
|
||||
{
|
||||
/* must be non-GPI index */
|
||||
/* 必须是非GPI指数 */
|
||||
Assert(!RelationIsGlobalIndex(idxrel));
|
||||
|
||||
fakeRelInfo->partList = GetPartitionList(rel, RowExclusiveLock);
|
||||
/* search the tuple in partition list one by one */
|
||||
/* 在分区列表中逐个搜索元组 */
|
||||
ListCell *cell = NULL;
|
||||
foreach (cell, fakeRelInfo->partList) {
|
||||
Partition heapPart = (Partition)lfirst(cell);
|
||||
Relation partionRel = RelationIsSubPartitioned(rel) ? SubPartitionGetRelation(rel, heapPart, NoLock) :
|
||||
partitionGetRelation(rel, heapPart);
|
||||
/* Get index partition of this heap partition */
|
||||
/* 获取此堆分区的索引分区 */
|
||||
Oid idxPartOid = getPartitionIndexOid(RelationGetRelid(idxrel), heapPart->pd_id);
|
||||
Partition idxPart = partitionOpen(idxrel, idxPartOid, RowExclusiveLock);
|
||||
Relation idxPartRel = RelationIsSubPartitioned(rel) ? SubPartitionGetRelation(idxrel, idxPart, NoLock) :
|
||||
|
|
@ -179,24 +218,28 @@ static bool PartitionFindReplTupleByIndex(EState *estate, Relation rel, Relation
|
|||
fakeRelInfo->partOid = heapPart->pd_id;
|
||||
|
||||
if (RelationFindReplTupleByIndex(estate, rel, idxPartRel, lockmode, searchslot, outslot, fakeRelInfo)) {
|
||||
/* Hit, release index resource, heap partition need to be used later, so don't release it */
|
||||
/* 命中,释放索引资源,堆分区需要以后使用,所以不要释放它 */
|
||||
partitionClose(idxrel, idxPart, NoLock);
|
||||
releaseDummyRelation(&idxPartRel);
|
||||
/* caller shoud release partRel */
|
||||
/* 调用方应释放部件Rel */
|
||||
fakeRelInfo->needRleaseDummyRel = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* didn't find tuple in current partition, release dummy relation and switch to next partition */
|
||||
/* 在当前分区中没有找到元组,释放虚拟关系并切换到下一个分区 */
|
||||
releaseDummyRelation(&fakeRelInfo->partRel);
|
||||
partitionClose(idxrel, idxPart, NoLock);
|
||||
releaseDummyRelation(&idxPartRel);
|
||||
}
|
||||
|
||||
/* do not find tuple in any patition, close and return */
|
||||
/* 没有找到元组在任何分区,关闭和返回 */
|
||||
releasePartitionList(rel, &fakeRelInfo->partList, NoLock);
|
||||
return false;
|
||||
}
|
||||
/* 这段代码是在分区表中根据索引查找元组的函数。首先,它断言索引不是全局分区索引。然后,它获取分区列表,并使用foreach循环遍历每个分区。
|
||||
在循环中,它获取当前分区的关系,并根据索引和分区的ID获取索引分区的OID。然后,它打开索引分区的关系,并将相关的信息设置到fakeRelInfo结构中。
|
||||
接下来,它调用RelationFindReplTupleByIndex函数来在当前分区的索引中查找匹配的元组。如果找到了匹配的元组,它会关闭索引分区的关系,并释放相关的资源。
|
||||
最后,它将fakeRelInfo->needRleaseDummyRel设置为true,表示调用方需要释放虚拟关系,并返回true表示找到了匹配的元组。*/
|
||||
|
||||
static bool PartitionFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
|
||||
TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelInfo)
|
||||
|
|
@ -213,25 +256,29 @@ static bool PartitionFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
|
|||
fakeRelInfo->partOid = heapPart->pd_id;
|
||||
|
||||
if (RelationFindReplTupleSeq(rel, lockmode, searchslot, outslot, fakeRelInfo)) {
|
||||
/* caller shoud release partRel */
|
||||
/* 调用方应释放部件Rel */
|
||||
fakeRelInfo->needRleaseDummyRel = true;
|
||||
return true;
|
||||
}
|
||||
releaseDummyRelation(&fakeRelInfo->partRel);
|
||||
}
|
||||
|
||||
/* do not find tuple in any patition, close and return */
|
||||
/* 没有找到元组在任何分区,关闭和返回 */
|
||||
releasePartitionList(rel, &fakeRelInfo->partList, NoLock);
|
||||
return false;
|
||||
}
|
||||
/* 这段代码是在分区表中按顺序查找元组的函数。它首先获取分区列表,并使用foreach循环遍历每个分区。
|
||||
在循环中,它获取当前分区的关系,并将相关的信息设置到fakeRelInfo结构中。
|
||||
然后,它调用RelationFindReplTupleSeq函数来在当前分区中按顺序查找匹配的元组。如果找到了匹配的元组,它将fakeRelInfo->needRleaseDummyRel设置为true,表示调用方需要释放虚拟关系,并返回true表示找到了匹配的元组。
|
||||
如果在当前分区中没有找到匹配的元组,它会释放虚拟关系。
|
||||
最后,如果在所有分区中都没有找到匹配的元组,它会释放分区列表,并返回false表示没有找到匹配的元组。*/
|
||||
|
||||
/*
|
||||
* Search the relation 'rel' for tuple using the index or seq scan.
|
||||
* 使用索引或序列扫描搜索关系'rel'查找元组。
|
||||
*
|
||||
* If a matching tuple is found, lock it with lockmode, fill the slot with its
|
||||
* contents, and return true. Return false otherwise.
|
||||
* 如果找到匹配的元组,用lockmode锁定它,用它的内容填充槽,并返回true。否则返回false。
|
||||
*
|
||||
* Caller should check and release fakeRelInfo->partList and fakeRelInfo->partRel
|
||||
* 调用者应该检查并释放fakeRelInfo->partList和fakeRelInfo-> parttrel
|
||||
*/
|
||||
bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMode lockmode,
|
||||
TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelInfo)
|
||||
|
|
@ -240,7 +287,7 @@ bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMo
|
|||
bool found = false;
|
||||
Relation idxrel = NULL;
|
||||
|
||||
/* clear fake rel info */
|
||||
/* 清除假rel信息 */
|
||||
rc = memset_s(fakeRelInfo, sizeof(FakeRelationPartition), 0, sizeof(FakeRelationPartition));
|
||||
securec_check(rc, "", "");
|
||||
|
||||
|
|
@ -248,7 +295,7 @@ bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMo
|
|||
idxrel = index_open(idxoid, RowExclusiveLock);
|
||||
}
|
||||
|
||||
/* for non partitioned table, or partitioned table with GPI, use parent heap and index to do the scan */
|
||||
/*对于非分区表或带有GPI的分区表,使用父堆和索引进行扫描 */
|
||||
if (RelationIsNonpartitioned(rel) || (idxrel != NULL && RelationIsGlobalIndex(idxrel))) {
|
||||
if (idxrel != NULL) {
|
||||
found = RelationFindReplTupleByIndex(estate, rel, idxrel, lockmode, searchslot, outslot, fakeRelInfo);
|
||||
|
|
@ -259,7 +306,7 @@ bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMo
|
|||
}
|
||||
}
|
||||
|
||||
/* scan with partition */
|
||||
/* 分区扫描 */
|
||||
if (idxrel != NULL) {
|
||||
found = PartitionFindReplTupleByIndex(estate, rel, idxrel, lockmode, searchslot, outslot, fakeRelInfo);
|
||||
index_close(idxrel, NoLock);
|
||||
|
|
@ -270,10 +317,9 @@ bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMo
|
|||
}
|
||||
|
||||
/*
|
||||
* Search the relation 'rel' for tuple using the index.
|
||||
* 使用索引搜索关系'rel'查找元组。
|
||||
*
|
||||
* If a matching tuple is found, lock it with lockmode, fill the slot with its
|
||||
* contents, and return true. Return false otherwise.
|
||||
* 如果找到匹配的元组,用lockmode锁定它,用它的内容填充槽,并返回true。否则返回false。
|
||||
*/
|
||||
static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation idxrel, LockTupleMode lockmode,
|
||||
TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelPart)
|
||||
|
|
@ -288,8 +334,7 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation
|
|||
int rc;
|
||||
bool isGpi = RelationIsGlobalIndex(idxrel);
|
||||
/*
|
||||
* For GPI and non-partition table, use parent heap relation to search the tuple,
|
||||
* otherwise use partition relation
|
||||
* 对于GPI和非分区表,使用父堆关系查找元组,否则使用分区关系
|
||||
*/
|
||||
if (isGpi || RelationIsNonpartitioned(rel)) {
|
||||
targetRel = rel;
|
||||
|
|
@ -297,21 +342,21 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation
|
|||
targetRel = fakeRelPart->partRel;
|
||||
}
|
||||
Assert(targetRel != NULL);
|
||||
/* Start an index scan. */
|
||||
/* 启动索引扫描。 */
|
||||
InitDirtySnapshot(snap);
|
||||
scan = scan_handler_idx_beginscan(targetRel, idxrel, &snap,
|
||||
IndexRelationGetNumberOfKeyAttributes(idxrel), 0);
|
||||
/* refer to check_violation, we need to set isUpsert if we want to use dirty snapshot in UStore */
|
||||
/* 参考check_violation,如果我们想在UStore中使用脏快照,我们需要设置isUpsert */
|
||||
scan->isUpsert = true;
|
||||
|
||||
/* Build scan key. */
|
||||
/* 构建扫描键。 */
|
||||
build_replindex_scan_key(skey, targetRel, idxrel, searchslot);
|
||||
|
||||
while (true) {
|
||||
found = false;
|
||||
scan_handler_idx_rescan(scan, skey, IndexRelationGetNumberOfKeyAttributes(idxrel), NULL, 0);
|
||||
|
||||
/* Try to find the tuple */
|
||||
/* 试着找到这个元组 */
|
||||
if (RelationIsUstoreFormat(targetRel)) {
|
||||
found = IndexGetnextSlot(scan, ForwardScanDirection, outslot);
|
||||
} else {
|
||||
|
|
@ -321,11 +366,10 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation
|
|||
}
|
||||
}
|
||||
if (found) {
|
||||
/* Found tuple, try to lock it in the lockmode. */
|
||||
/* 找到元组,尝试在锁定模式下锁定它。 */
|
||||
xwait = TransactionIdIsValid(snap.xmin) ? snap.xmin : snap.xmax;
|
||||
/*
|
||||
* If the tuple is locked, wait for locking transaction to finish
|
||||
* and retry.
|
||||
* 如果元组被锁定,请等待锁定事务完成后重试。
|
||||
*/
|
||||
if (TransactionIdIsValid(xwait)) {
|
||||
XactLockTableWait(xwait);
|
||||
|
|
@ -345,7 +389,7 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation
|
|||
ItemPointer tid = tableam_tops_get_t_self(targetRel, outslot->tts_tuple);
|
||||
|
||||
if (RelationIsUstoreFormat(targetRel)) {
|
||||
/* materialize the slot, so we can visit it after the scan is end */
|
||||
/* 将插槽物化,这样扫描结束后我们就可以访问它了 */
|
||||
outslot->tts_tuple = UHeapMaterialize(outslot);
|
||||
ItemPointerCopy(tid, &UHeaplocktup.ctid);
|
||||
rc = memset_s(&tbuf, sizeof(tbuf), 0, sizeof(tbuf));
|
||||
|
|
@ -353,13 +397,13 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation
|
|||
UHeaplocktup.disk_tuple = &tbuf.hdr;
|
||||
locktup = &UHeaplocktup;
|
||||
} else {
|
||||
/* materialize the slot, so we can visit it after the scan is end */
|
||||
/* 将插槽物化,这样扫描结束后我们就可以访问它了 */
|
||||
outslot->tts_tuple = ExecMaterializeSlot(outslot);
|
||||
ItemPointerCopy(tid, &heaplocktup.t_self);
|
||||
locktup = &heaplocktup;
|
||||
}
|
||||
|
||||
/* Get the target tuple's partition for GPI */
|
||||
/* 获取目标元组的GPI分区 */
|
||||
if (isGpi) {
|
||||
GetFakeRelAndPart(estate, rel, outslot, fakeRelPart);
|
||||
targetRel = fakeRelPart->partRel;
|
||||
|
|
@ -368,20 +412,20 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation
|
|||
PushActiveSnapshot(GetLatestSnapshot());
|
||||
res = tableam_tuple_lock(targetRel,
|
||||
locktup, &buf, GetCurrentCommandId(false), lockmode, false, &hufd,
|
||||
false, false, /* don't follow updates */
|
||||
false, /* eval */
|
||||
GetLatestSnapshot(), tid, /* ItemPointer */
|
||||
false); /* is select for update */
|
||||
/* the tuple slot already has the buffer pinned */
|
||||
false, false, /* 不要关注更新 */
|
||||
false, /* 评估 */
|
||||
GetLatestSnapshot(), tid, /* 项目指针 */
|
||||
false); /* 选择进行更新 */
|
||||
/* 元组槽已固定缓冲区 */
|
||||
ReleaseBuffer(buf);
|
||||
PopActiveSnapshot();
|
||||
|
||||
if (CheckTupleLockRes(res)) {
|
||||
/* lock tuple failed, try again */
|
||||
/* 锁定元组失败,请重试 */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* we are done */
|
||||
/* 我们结束了 */
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -390,7 +434,7 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation
|
|||
}
|
||||
|
||||
/*
|
||||
* Compare the tuple and slot and check if they have equal values.
|
||||
* 比较元组和槽,并检查它们是否具有相等的值。
|
||||
*/
|
||||
static bool tuple_equals_slot(TupleDesc desc, const Tuple tup, TupleTableSlot *slot, TypeCacheEntry **eq)
|
||||
{
|
||||
|
|
@ -401,22 +445,21 @@ static bool tuple_equals_slot(TupleDesc desc, const Tuple tup, TupleTableSlot *s
|
|||
|
||||
tableam_tops_deform_tuple(tup, desc, values, isnull);
|
||||
|
||||
/* Check equality of the attributes. */
|
||||
/* 检查属性的相等性。 */
|
||||
for (attrnum = 0; attrnum < desc->natts; attrnum++) {
|
||||
TypeCacheEntry *typentry;
|
||||
/* skip generate column */
|
||||
/* 跳过生成列跳过生成列 */
|
||||
if (GetGeneratedCol(desc, attrnum)) {
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* If one value is NULL and other is not, then they are certainly not
|
||||
* equal
|
||||
* 如果一个值为NULL,另一个值不为NULL,那么它们肯定不相等
|
||||
*/
|
||||
if (isnull[attrnum] != slot->tts_isnull[attrnum])
|
||||
return false;
|
||||
|
||||
/*
|
||||
* If both are NULL, they can be considered equal.
|
||||
* 如果两者都为NULL,则可以认为它们相等。
|
||||
*/
|
||||
if (isnull[attrnum])
|
||||
continue;
|
||||
|
|
@ -442,14 +485,13 @@ static bool tuple_equals_slot(TupleDesc desc, const Tuple tup, TupleTableSlot *s
|
|||
}
|
||||
|
||||
/*
|
||||
* Search the relation 'rel' for tuple using the sequential scan.
|
||||
* 使用顺序扫描在关系“rel”中搜索元组。
|
||||
*
|
||||
* If a matching tuple is found, lock it with lockmode, fill the slot with its
|
||||
* contents, and return true. Return false otherwise.
|
||||
* 如果找到匹配的元组,请使用lockmode将其锁定,用其内容填充槽,然后返回true。否则返回false。
|
||||
*
|
||||
* Note that this stops on the first matching tuple.
|
||||
* 请注意,这在第一个匹配元组上停止。
|
||||
*
|
||||
* This can obviously be quite slow on tables that have more than few rows.
|
||||
* 对于行数多于几行的表,这显然会非常缓慢。
|
||||
*/
|
||||
static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, TupleTableSlot *searchslot,
|
||||
TupleTableSlot *outslot, FakeRelationPartition *fakeRelPart)
|
||||
|
|
@ -468,7 +510,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple
|
|||
Assert(equalTupleDescs(desc, outslot->tts_tupleDescriptor));
|
||||
eq = (TypeCacheEntry **)palloc0(sizeof(*eq) * outslot->tts_tupleDescriptor->natts);
|
||||
|
||||
/* Start a heap scan. */
|
||||
/* 启动堆扫描。 */
|
||||
InitDirtySnapshot(snap);
|
||||
scan = scan_handler_tbl_beginscan(targetRel, &snap, 0, NULL, NULL);
|
||||
|
||||
|
|
@ -477,7 +519,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple
|
|||
found = false;
|
||||
scan_handler_tbl_rescan(scan, NULL, targetRel);
|
||||
|
||||
/* Try to find the tuple */
|
||||
/* 尝试查找元组 */
|
||||
while ((scantuple = scan_handler_tbl_getnext(scan, ForwardScanDirection, targetRel)) != NULL) {
|
||||
if (!tuple_equals_slot(desc, scantuple, searchslot, eq)) {
|
||||
continue;
|
||||
|
|
@ -488,8 +530,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple
|
|||
|
||||
xwait = TransactionIdIsValid(snap.xmin) ? snap.xmin : snap.xmax;
|
||||
/*
|
||||
* If the tuple is locked, wait for locking transaction to finish
|
||||
* and retry.
|
||||
* 如果元组已锁定,请等待锁定事务完成,然后重试。
|
||||
*/
|
||||
if (TransactionIdIsValid(xwait)) {
|
||||
/* retry */
|
||||
|
|
@ -503,7 +544,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple
|
|||
continue;
|
||||
}
|
||||
if (found) {
|
||||
/* Found tuple, try to lock it in the lockmode. */
|
||||
/* 找到元组,请尝试在锁定模式下锁定它。 */
|
||||
Buffer buf;
|
||||
TM_FailureData hufd;
|
||||
TM_Result res;
|
||||
|
|
@ -517,7 +558,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple
|
|||
ItemPointer tid = tableam_tops_get_t_self(rel, outslot->tts_tuple);
|
||||
|
||||
if (RelationIsUstoreFormat(targetRel)) {
|
||||
/* materialize the slot, so we can visit it after the scan is end */
|
||||
/* 具体化插槽,这样我们就可以在扫描结束后访问它 */
|
||||
outslot->tts_tuple = UHeapMaterialize(outslot);
|
||||
ItemPointerCopy(tid, &UHeaplocktup.ctid);
|
||||
rc = memset_s(&tbuf, sizeof(tbuf), 0, sizeof(tbuf));
|
||||
|
|
@ -525,7 +566,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple
|
|||
UHeaplocktup.disk_tuple = &tbuf.hdr;
|
||||
locktup = &UHeaplocktup;
|
||||
} else {
|
||||
/* materialize the slot, so we can visit it after the scan is end */
|
||||
/* 具体化插槽,这样我们就可以在扫描结束后访问它 */
|
||||
outslot->tts_tuple = ExecMaterializeSlot(outslot);
|
||||
ItemPointerCopy(tid, &heaplocktup.t_self);
|
||||
locktup = &heaplocktup;
|
||||
|
|
@ -534,21 +575,21 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple
|
|||
PushActiveSnapshot(GetLatestSnapshot());
|
||||
res = tableam_tuple_lock(targetRel, locktup, &buf, GetCurrentCommandId(false),
|
||||
lockmode, false, &hufd, false,
|
||||
false, /* don't follow updates */
|
||||
false, /* eval */
|
||||
GetLatestSnapshot(), tid, /* ItemPointer */
|
||||
false); /* is select for update */
|
||||
false, /* 不关注更新 */
|
||||
false, /* 评估 */
|
||||
GetLatestSnapshot(), tid, /* 项目指针 */
|
||||
false); /* 选择进行更新 */
|
||||
|
||||
/* the tuple slot already has the buffer pinned */
|
||||
/* 元组槽已固定缓冲区 */
|
||||
ReleaseBuffer(buf);
|
||||
PopActiveSnapshot();
|
||||
|
||||
if (CheckTupleLockRes(res)) {
|
||||
/* lock tuple failed, try again */
|
||||
/* 锁定元组失败,请重试 */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* we are done */
|
||||
/* 我们结束了 */
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -558,10 +599,9 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple
|
|||
}
|
||||
|
||||
/*
|
||||
* Insert tuple represented in the slot to the relation, update the indexes,
|
||||
* and execute any constraints and per-row triggers.
|
||||
* 将槽中表示的元组插入关系,更新索引,并执行任何约束和每行触发器。
|
||||
*
|
||||
* Caller is responsible for opening the indexes.
|
||||
* 调用者负责打开索引。
|
||||
*/
|
||||
void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot, FakeRelationPartition *relAndPart)
|
||||
{
|
||||
|
|
@ -570,12 +610,12 @@ void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot, FakeRelation
|
|||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
Relation targetRel = relAndPart->partRel == NULL ? rel : relAndPart->partRel;
|
||||
|
||||
/* For now we support only tables. */
|
||||
/* 目前,我们只支持表格。 */
|
||||
Assert(rel->rd_rel->relkind == RELKIND_RELATION);
|
||||
|
||||
CheckCmdReplicaIdentity(rel, CMD_INSERT);
|
||||
|
||||
/* BEFORE ROW INSERT Triggers */
|
||||
/* 在行之前插入触发器 */
|
||||
if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row) {
|
||||
slot = ExecBRInsertTriggers(estate, resultRelInfo, slot);
|
||||
if (slot == NULL) {
|
||||
|
|
@ -583,40 +623,43 @@ void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot, FakeRelation
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List *recheckIndexes = NIL;
|
||||
/* Materialize slot into a tuple that we can scribble upon. */
|
||||
/*这段代码是用于执行简单的关系表插入操作。它首先获取要插入的元组和目标关系表,
|
||||
然后检查关系表的复制标识以确保插入操作是合法的。接着,它在插入行之前执行插入触发器。
|
||||
如果存在触发器并且它们是在行之前执行的,则调用ExecBRInsertTriggers函数来执行这些触发器。
|
||||
如果插入操作被触发器取消,则返回。*/
|
||||
|
||||
List *recheckIndexes = NIL;
|
||||
/* 将槽具体化为一个元组,我们可以在上面乱涂乱画。 */
|
||||
tuple = tableam_tslot_get_tuple_from_slot(rel, slot);
|
||||
tableam_tops_update_tuple_with_oid(targetRel, tuple, slot);
|
||||
|
||||
/* Compute stored generated columns */
|
||||
/* 计算存储的生成列 */
|
||||
if (rel->rd_att->constr && rel->rd_att->constr->has_generated_stored) {
|
||||
ExecComputeStoredGenerated(resultRelInfo, estate, slot, tuple, CMD_INSERT);
|
||||
tuple = slot->tts_tuple;
|
||||
}
|
||||
|
||||
/* Check the constraints of the tuple */
|
||||
/* 检查元组的约束 */
|
||||
if (rel->rd_att->constr)
|
||||
ExecConstraints(resultRelInfo, slot, estate);
|
||||
|
||||
/* OK, store the tuple and create index entries for it */
|
||||
/* 好的,存储元组并为其创建索引项 */
|
||||
(void)tableam_tuple_insert(targetRel, tuple, GetCurrentCommandId(true), 0, NULL);
|
||||
if (resultRelInfo->ri_NumIndices > 0) {
|
||||
ItemPointer pTSelf = tableam_tops_get_t_self(rel, tuple);
|
||||
recheckIndexes =
|
||||
ExecInsertIndexTuples(slot, pTSelf, estate, targetRel, relAndPart->part, InvalidBktId, NULL, NULL);
|
||||
}
|
||||
/* AFTER ROW INSERT Triggers */
|
||||
/* 在行后插入触发器 */
|
||||
ExecARInsertTriggers(estate, resultRelInfo, relAndPart->partOid, InvalidBktId, (HeapTuple)tuple, recheckIndexes);
|
||||
|
||||
list_free_ext(recheckIndexes);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the searchslot tuple and update it with data in the slot,
|
||||
* update the indexes, and execute any constraints and per-row triggers.
|
||||
* 查找searchslot元组,并使用slot中的数据对其进行更新,更新索引,并执行任何约束和每行触发器。
|
||||
*
|
||||
* Caller is responsible for opening the indexes.
|
||||
* 调用者负责打开索引。
|
||||
*/
|
||||
void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot *searchslot, TupleTableSlot *slot,
|
||||
FakeRelationPartition *relAndPart)
|
||||
|
|
@ -627,7 +670,7 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
ItemPointer searchSlotTid = tableam_tops_get_t_self(rel, searchslot->tts_tuple);
|
||||
|
||||
/* For now we support only tables. */
|
||||
/* 目前,我们只支持表格。 */
|
||||
Assert(rel->rd_rel->relkind == RELKIND_RELATION);
|
||||
|
||||
CheckCmdReplicaIdentity(rel, CMD_UPDATE);
|
||||
|
|
@ -637,7 +680,7 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
allowInplaceUpdate = false;
|
||||
}
|
||||
|
||||
/* BEFORE ROW UPDATE Triggers */
|
||||
/* 排前更新触发器 */
|
||||
if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_update_before_row) {
|
||||
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, relAndPart->partOid, InvalidBktId, NULL,
|
||||
searchSlotTid, slot);
|
||||
|
|
@ -646,8 +689,13 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Materialize slot into a tuple that we can scribble upon. */
|
||||
/* 这段代码是用于执行简单的关系表更新操作。它首先获取要更新的元组和目标关系表,然后检查关系表的复制标识以确保更新操作是合法的。
|
||||
接着,它检查是否允许原地更新,如果不允许,则需要进行分裂更新。
|
||||
接下来,它执行更新之前的触发器,并在更新之前检查是否需要进行分裂更新。
|
||||
如果存在触发器并且它们是在行之前执行的,则调用ExecBRUpdateTriggers函数来执行这些触发器。
|
||||
如果更新操作被触发器取消,则返回。 */
|
||||
|
||||
/* 将槽具体化为一个元组,我们可以在上面乱涂乱画。 */
|
||||
tuple = tableam_tslot_get_tuple_from_slot(rel, slot);
|
||||
List *recheckIndexes = NIL;
|
||||
Bitmapset *modifiedIdxAttrs = NULL;
|
||||
|
|
@ -660,17 +708,17 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
Relation targetRelation = relAndPart->partRel == NULL ? rel : relAndPart->partRel;
|
||||
Relation parentRelation = relAndPart->partRel == NULL ? NULL : rel;
|
||||
|
||||
/* Compute stored generated columns */
|
||||
/* 计算存储的生成列 */
|
||||
if (rel->rd_att->constr && rel->rd_att->constr->has_generated_stored) {
|
||||
ExecComputeStoredGenerated(resultRelInfo, estate, slot, tuple, CMD_UPDATE);
|
||||
}
|
||||
|
||||
/* Check the constraints of the tuple */
|
||||
/* 检查元组的约束 */
|
||||
if (rel->rd_att->constr) {
|
||||
ExecConstraints(resultRelInfo, slot, estate);
|
||||
}
|
||||
|
||||
/* check whether there is a row movement for partition table */
|
||||
/* 检查分区表是否有行移动 */
|
||||
GetFakeRelAndPart(estate, rel, slot, &newTupleInfo);
|
||||
if (newTupleInfo.partOid != InvalidOid && newTupleInfo.partOid != relAndPart->partOid) {
|
||||
if (!rel->rd_rel->relrowmovement) {
|
||||
|
|
@ -680,10 +728,15 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
}
|
||||
rowMovement = true;
|
||||
}
|
||||
|
||||
/* 这段代码是用于获取虚拟关系表和分区信息。
|
||||
它首先获取要插入或更新的元组,然后调用GetRelationPartitionOid函数来获取元组所属的分区OID。
|
||||
如果元组属于一个分区,则获取该分区的FakeRelationPartition信息。
|
||||
接着,它检查如果新元组所属的分区与目标分区不同,则需要进行行移动操作。
|
||||
如果关系表没有启用行移动,则会抛出错误。 */
|
||||
|
||||
tuple = slot->tts_tuple;
|
||||
CommandId cid = GetCurrentCommandId(true);
|
||||
/* OK, update the tuple and index entries for it */
|
||||
/* 好的,更新它的元组和索引项 */
|
||||
if (!rowMovement) {
|
||||
res = tableam_tuple_update(targetRelation, parentRelation, searchSlotTid, tuple, cid,
|
||||
InvalidSnapshot, estate->es_snapshot, true, &oldslot, &tmfd, &updateIndexes, &modifiedIdxAttrs,
|
||||
|
|
@ -700,7 +753,7 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
searchSlotTid, exec_index_tuples_state, InvalidBktId, modifiedIdxAttrs);
|
||||
}
|
||||
} else {
|
||||
/* rowMovement, delete origin tuple and insert new */
|
||||
/* rowMovement,删除原始元组并插入新元组 */
|
||||
Assert(relAndPart->partRel != NULL);
|
||||
Assert(newTupleInfo.partRel != NULL);
|
||||
res = tableam_tuple_delete(relAndPart->partRel, searchSlotTid, cid, InvalidSnapshot,
|
||||
|
|
@ -715,7 +768,7 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
tableam_tops_exec_delete_index_tuples(oldslot, relAndPart->partRel, NULL, searchSlotTid,
|
||||
exec_index_tuples_state, modifiedIdxAttrs);
|
||||
|
||||
/* Insert new tuple */
|
||||
/* 插入新元组 */
|
||||
(void)tableam_tuple_insert(newTupleInfo.partRel, tuple, cid, 0, NULL);
|
||||
if (resultRelInfo->ri_NumIndices > 0) {
|
||||
ItemPointer pTSelf = tableam_tops_get_t_self(rel, tuple);
|
||||
|
|
@ -727,8 +780,12 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
if (oldslot) {
|
||||
ExecDropSingleTupleTableSlot(oldslot);
|
||||
}
|
||||
|
||||
/* AFTER ROW UPDATE Triggers */
|
||||
/* 这段代码是用于向关系表中插入新的元组。
|
||||
它首先调用tableam_tuple_insert函数将元组插入到目标关系表中。
|
||||
如果目标关系表有索引,则调用ExecInsertIndexTuples函数来为插入的元组创建索引。
|
||||
最后,它释放旧的插槽。*/
|
||||
|
||||
/* 排后更新触发器 */
|
||||
ExecARUpdateTriggers(estate, resultRelInfo, relAndPart->partOid, InvalidBktId, relAndPart->partOid,
|
||||
searchSlotTid, (HeapTuple)tuple, NULL, recheckIndexes);
|
||||
|
||||
|
|
@ -736,10 +793,9 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
}
|
||||
|
||||
/*
|
||||
* Find the searchslot tuple and delete it, and execute any constraints
|
||||
* and per-row triggers.
|
||||
* .找到searchslot元组并将其删除,然后执行任何约束和每行触发器。
|
||||
*
|
||||
* Caller is responsible for opening the indexes.
|
||||
* 调用者负责打开索引。
|
||||
*/
|
||||
void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, TupleTableSlot *searchslot,
|
||||
FakeRelationPartition *relAndPart)
|
||||
|
|
@ -749,12 +805,12 @@ void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
ItemPointer tid = tableam_tops_get_t_self(rel, searchslot->tts_tuple);
|
||||
|
||||
/* For now we support only tables. */
|
||||
/* 目前,我们只支持表格。 */
|
||||
Assert(rel->rd_rel->relkind == RELKIND_RELATION);
|
||||
|
||||
CheckCmdReplicaIdentity(rel, CMD_DELETE);
|
||||
|
||||
/* BEFORE ROW INSERT Triggers */
|
||||
/* 在行之前插入触发器 */
|
||||
if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_delete_before_row) {
|
||||
skip_tuple =
|
||||
!ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, relAndPart->partOid, InvalidBktId, NULL, tid);
|
||||
|
|
@ -767,7 +823,7 @@ void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
Relation targetRel = relAndPart->partRel == NULL ? rel : relAndPart->partRel;
|
||||
TM_FailureData tmfd;
|
||||
|
||||
/* OK, delete the tuple */
|
||||
/* 好,删除元组 */
|
||||
TM_Result res = tableam_tuple_delete(targetRel, tid, GetCurrentCommandId(true), InvalidSnapshot,
|
||||
estate->es_snapshot, true, &oldslot, &tmfd);
|
||||
CheckTupleModifyRes(res);
|
||||
|
|
@ -783,29 +839,29 @@ void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, TupleTableSlot
|
|||
ExecDropSingleTupleTableSlot(oldslot);
|
||||
}
|
||||
|
||||
/* AFTER ROW DELETE Triggers */
|
||||
/* 行删除触发器之后 */
|
||||
ExecARDeleteTriggers(estate, resultRelInfo, relAndPart->partOid, InvalidBktId, NULL, tid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if command can be executed with current replica identity.
|
||||
* 检查是否可以使用当前副本标识执行命令。
|
||||
*/
|
||||
void CheckCmdReplicaIdentity(Relation rel, CmdType cmd)
|
||||
{
|
||||
PublicationActions *pubactions;
|
||||
|
||||
/* We only need to do checks for UPDATE and DELETE. */
|
||||
/* 我们只需要检查UPDATE和DELETE。 */
|
||||
if (cmd != CMD_UPDATE && cmd != CMD_DELETE)
|
||||
return;
|
||||
|
||||
/* If relation has replica identity we are always good. */
|
||||
/* 若关系具有复制身份,我们总是好的。 */
|
||||
if (RelationGetRelReplident(rel) == REPLICA_IDENTITY_FULL || OidIsValid(RelationGetReplicaIndex(rel)))
|
||||
return;
|
||||
|
||||
/*
|
||||
* This is either UPDATE OR DELETE and there is no replica identity.
|
||||
* 这是UPDATE或DELETE,并且没有副本标识。
|
||||
*
|
||||
* Check if the table publishes UPDATES or DELETES.
|
||||
* 检查表是否发布UPDATES或DELETES。
|
||||
*/
|
||||
pubactions = GetRelationPublicationActions(rel);
|
||||
if (cmd == CMD_UPDATE && pubactions->pubupdate) {
|
||||
|
|
@ -830,8 +886,13 @@ void GetFakeRelAndPart(EState *estate, Relation rel, TupleTableSlot *slot, FakeR
|
|||
|
||||
if (RelationIsNonpartitioned(rel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
/* 此代码片段定义了一个名为GetFakeRelAndPart的函数,
|
||||
该函数接受EState对象、Relation对象、TupleTableSlot对象和FakeRelationPartition对象作为输入参数。
|
||||
函数首先将FakeRelationPartition对象的partRel、part和partOid属性分别初始化为NULL和InvalidOid。
|
||||
如果输入的Relation对象是非分区的,则函数只返回而不执行任何其他操作。
|
||||
否则,它将继续确定当前元组所属的分区。*/
|
||||
|
||||
Relation partRelation = NULL;
|
||||
Partition partition = NULL;
|
||||
Oid partitionOid;
|
||||
|
|
@ -871,3 +932,13 @@ void GetFakeRelAndPart(EState *estate, Relation rel, TupleTableSlot *slot, FakeR
|
|||
break;
|
||||
}
|
||||
}
|
||||
/* 此代码段继续实现“GetFakeElAndPart”函数。
|
||||
它首先声明了几个变量,包括“partRelation”、“partition”和“partitionOid”,这些变量用于存储有关分区关系及其分区的信息。
|
||||
然后,它使用“tableam_tslot_get_tuple_from_slot”函数从“TupleTableSlot”对象检索当前元组。
|
||||
然后,函数输入一个switch语句,该语句检查输入“Relation”对象的“parttype”属性。
|
||||
如果它是一个未分区或值分区的关系,则函数只需脱离switch语句。
|
||||
如果关系是分区关系,则函数使用“heapTupleGetPartitionId”函数检索当前元组的“partitionOid”。
|
||||
然后,它调用“searchFakeRetreationForPartitionOid”函数来搜索分区关系及其基于“partitionOid”的相应分区。
|
||||
函数将结果分别存储在“partRelation”和“partition”变量中。然后,它将“FakeRelationPartition”对象的“partRel”、“part”和“partOid”属性设置为相应的值。
|
||||
如果关系是子划分关系,则函数执行与划分关系情况类似的过程,但它首先使用“partitionOid”搜索划分关系,然后使用从当前元组检索到的“subPartOid”来搜索子划分关系。
|
||||
如果输入“Relation”对象的“parttype”属性无法识别,则函数会使用“ereport”函数引发错误。 */
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
/* -------------------------------------------------------------------------
|
||||
*
|
||||
* execScan.cpp
|
||||
* This code provides support for generalized relation scans. ExecScan
|
||||
* is passed a node and a pointer to a function to "do the right thing"
|
||||
* and return a tuple from the relation. ExecScan then does the tedious
|
||||
* stuff - checking the qualification and projecting the tuple
|
||||
* appropriately.
|
||||
* 此代码提供对广义关系扫描的支持。ExecScan被传递一个节点和一个指向函数的指针,
|
||||
* 以“做正确的事情”并从关系中返回一个元组。
|
||||
* ExecScan然后做一些乏味的工作——检查资格并适当地投影元组。
|
||||
*
|
||||
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
* 部分版权所有(c)2020华为技术有限公司有限公司。
|
||||
* 部分版权所有(c)1996-2012,PostgreSQL 全球开发集团
|
||||
* 部分版权所有(c)1994,加州大学董事会
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
|
|
@ -25,21 +23,30 @@
|
|||
#include "utils/memutils.h"
|
||||
|
||||
/*
|
||||
* ExecScanFetch -- fetch next potential tuple
|
||||
* ExecScanFetch -- 获取下一个潜在元组
|
||||
*
|
||||
* This routine is concerned with substituting a test tuple if we are
|
||||
* inside an EvalPlanQual recheck. If we aren't, just execute
|
||||
* the access method's next-tuple routine.
|
||||
* 如果我们在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) {
|
||||
/*
|
||||
* We are inside an EvalPlanQual recheck. Return the test tuple if
|
||||
* one is available, after rechecking any access-method-specific
|
||||
* conditions.
|
||||
* 我们正在进行EvalPlanQual复查。
|
||||
* 在重新检查任何特定于访问方法的条件后,返回测试元组(如果有)。
|
||||
*/
|
||||
Index scan_rel_id = ((Scan*)node->ps.plan)->scanrelid;
|
||||
|
||||
|
|
@ -47,29 +54,29 @@ static TupleTableSlot* ExecScanFetch(ScanState* node, ExecScanAccessMtd access_m
|
|||
if (estate->es_epqTupleSet[scan_rel_id - 1]) {
|
||||
TupleTableSlot* slot = node->ss_ScanTupleSlot;
|
||||
|
||||
/* Return empty slot if we already returned a tuple */
|
||||
/* 如果我们已经返回了元组,则返回空槽 */
|
||||
if (estate->es_epqScanDone[scan_rel_id - 1])
|
||||
return ExecClearTuple(slot);
|
||||
/* Else mark to remember that we shouldn't return more */
|
||||
return ExecClearTuple(slot);如果我们没有测试元组,则返回空槽
|
||||
/* 否则请记住,我们不应该再回来了 */
|
||||
estate->es_epqScanDone[scan_rel_id - 1] = true;
|
||||
|
||||
/* Return empty slot if we haven't got a test tuple */
|
||||
/* 如果我们没有测试元组,则返回空槽 */
|
||||
if (estate->es_epqTuple[scan_rel_id - 1] == NULL)
|
||||
return ExecClearTuple(slot);
|
||||
|
||||
/* Store test tuple in the plan node's scan slot */
|
||||
/* 将测试元组存储在计划节点的扫描槽中 */
|
||||
(void)ExecStoreTuple(estate->es_epqTuple[scan_rel_id - 1], slot, InvalidBuffer, false);
|
||||
|
||||
/* Check if it meets the access-method conditions */
|
||||
/* 检查是否符合访问方法条件 */
|
||||
if (!(*recheck_mtd)(node, slot))
|
||||
(void)ExecClearTuple(slot); /* would not be returned by scan */
|
||||
(void)ExecClearTuple(slot); /* 不会通过扫描返回 */
|
||||
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Run the node-type-specific access method function to get the next tuple
|
||||
* 运行特定于节点类型的访问方法函数以获取下一个元组
|
||||
*/
|
||||
return (*access_mtd)(node);
|
||||
}
|
||||
|
|
@ -77,27 +84,28 @@ static TupleTableSlot* ExecScanFetch(ScanState* node, ExecScanAccessMtd access_m
|
|||
/* ----------------------------------------------------------------
|
||||
* ExecScan
|
||||
*
|
||||
* Scans the relation using the 'access method' indicated and
|
||||
* returns the next qualifying tuple in the direction specified
|
||||
* in the global variable ExecDirection.
|
||||
* The access method returns the next tuple and execScan() is
|
||||
* responsible for checking the tuple returned against the qual-clause.
|
||||
* 使用指示的“访问方法”扫描关系,并按全局变量ExecDirection中指定的方向返回下一个符合条件的元组。
|
||||
* access方法返回下一个元组,execScan()负责根据qual子句检查返回的元组。
|
||||
*
|
||||
* A 'recheck method' must also be provided that can check an
|
||||
* arbitrary tuple of the relation against any qual conditions
|
||||
* that are implemented internal to the access method.
|
||||
* 还必须提供一个“重新检查方法”,该方法可以根据访问方法内部实现的任何qual条件检查关系的任意元组。
|
||||
*
|
||||
* Conditions:
|
||||
* -- the "cursor" maintained by the AMI is positioned at the tuple
|
||||
* returned previously.
|
||||
* 条件:
|
||||
* -- AMI维护的“游标”位于先前返回的元组处。
|
||||
*
|
||||
* Initial States:
|
||||
* -- the relation indicated is opened for scanning so that the
|
||||
* "cursor" is positioned before the first qualifying tuple.
|
||||
* 初始状态:
|
||||
* -- 所指示的关系被打开进行扫描,以便“光标”位于第一个符合条件的元组之前
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* function returning a tuple */
|
||||
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;
|
||||
|
|
@ -109,15 +117,14 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct
|
|||
return NULL;
|
||||
|
||||
/*
|
||||
* Fetch data from node
|
||||
* 从节点获取数据
|
||||
*/
|
||||
qual = node->ps.qual;
|
||||
proj_info = node->ps.ps_ProjInfo;
|
||||
econtext = node->ps.ps_ExprContext;
|
||||
|
||||
/*
|
||||
* If we have neither a qual to check nor a projection to do, just skip
|
||||
* all the overhead and return the raw scan tuple.
|
||||
* 如果我们既没有要检查的qual,也没有要做的投影,只需跳过所有开销并返回原始扫描元组。
|
||||
*/
|
||||
if (qual == NULL && proj_info == NULL) {
|
||||
ResetExprContext(econtext);
|
||||
|
|
@ -125,38 +132,36 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct
|
|||
}
|
||||
|
||||
/*
|
||||
* Check to see if we're still projecting out tuples from a previous scan
|
||||
* tuple (because there is a function-returning-set in the projection
|
||||
* expressions). If so, try to project another one.
|
||||
* 检查我们是否仍在从上一个扫描元组中投影出元组
|
||||
*(因为在投影表达式中有一个函数返回集)。
|
||||
* u如果是,试着投影另一个。
|
||||
*/
|
||||
if (node->ps.ps_TupFromTlist) {
|
||||
Assert(proj_info); /* can't get here if not projecting */
|
||||
Assert(proj_info); /* 如果不投影就不能到达这里 */
|
||||
result_slot = ExecProject(proj_info, &is_done);
|
||||
if (is_done == ExprMultipleResult)
|
||||
return result_slot;
|
||||
/* Done with that source tuple... */
|
||||
/* 已完成该源元组... */
|
||||
node->ps.ps_TupFromTlist = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* @hdfs
|
||||
* Optimize scan bu using informational constraint.
|
||||
* if the is_scan_false is true, the iteration is over.
|
||||
* 使用信息约束优化扫描bu。
|
||||
* 如果isscanfalse为true,则迭代结束。
|
||||
*/
|
||||
if (node->is_scan_end) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset per-tuple memory context to free any expression evaluation
|
||||
* storage allocated in the previous tuple cycle. Note this can't happen
|
||||
* until we're done projecting out tuples from a scan tuple.
|
||||
* 重置每个元组内存上下文以释放在上一个元组周期中分配的任何表达式求值存储。
|
||||
* 请注意,在我们完成从扫描元组中投影出元组之前,这是不可能发生的。
|
||||
*/
|
||||
ResetExprContext(econtext);
|
||||
|
||||
/*
|
||||
* get a tuple from the access method. Loop until we obtain a tuple that
|
||||
* passes the qualification.
|
||||
* 从access方法获取一个元组。循环直到我们获得一个通过资格的元组。
|
||||
*/
|
||||
for (;;) {
|
||||
TupleTableSlot* slot = NULL;
|
||||
|
|
@ -164,13 +169,11 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct
|
|||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
slot = ExecScanFetch(node, access_mtd, recheck_mtd);
|
||||
/* refresh qual every loop */
|
||||
/* 刷新qual每个循环 */
|
||||
qual = node->ps.qual;
|
||||
/*
|
||||
* if the slot returned by the accessMtd contains NULL, then it means
|
||||
* there is nothing more to scan so we just return an empty slot,
|
||||
* being careful to use the projection result slot so it has correct
|
||||
* tupleDesc.
|
||||
* 如果accessMtd返回的槽包含NULL,那么这意味着没有更多的东西可以扫描,
|
||||
* 所以我们只返回一个空槽,小心使用投影结果槽,这样它就有了正确的tupleDesc。
|
||||
*/
|
||||
if (TupIsNull(slot) || unlikely(executorEarlyStop())) {
|
||||
if (proj_info != NULL)
|
||||
|
|
@ -180,30 +183,28 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct
|
|||
}
|
||||
|
||||
/*
|
||||
* place the current tuple into the expr context
|
||||
* 将当前元组放入expr上下文
|
||||
*/
|
||||
econtext->ecxt_scantuple = slot;
|
||||
|
||||
/*
|
||||
* check that the current tuple satisfies the qual-clause
|
||||
* 检查当前元组是否满足qual子句
|
||||
*
|
||||
* check for non-nil qual here to avoid a function call to ExecQual()
|
||||
* when the qual is nil ... saves only a few cycles, but they add up
|
||||
* 在此处检查非nil qual,以避免在qual为nil时调用ExecQual()函数...只节省了几个周期,但它们加起来
|
||||
* ...
|
||||
*/
|
||||
if (qual == NULL || ExecQual(qual, econtext, false)) {
|
||||
/*
|
||||
* Found a satisfactory scan tuple.
|
||||
* 找到一个令人满意的扫描元组。
|
||||
*/
|
||||
if (proj_info != NULL) {
|
||||
/*
|
||||
* Form a projection tuple, store it in the result tuple slot
|
||||
* and return it --- unless we find we can project no tuples
|
||||
* from this scan tuple, in which case continue scan.
|
||||
* 形成一个投影元组,将其存储在结果元组槽中并返回它——除非我们发现我们不能从这个扫描元组中投影任何元组,
|
||||
* 在这种情况下,继续扫描。
|
||||
*/
|
||||
result_slot = ExecProject(proj_info, &is_done);
|
||||
#ifdef PGXC
|
||||
/* Copy the xcnodeoid if underlying scanned slot has one */
|
||||
/* 复制xcnodeoid(如果底层扫描的插槽有一个) */
|
||||
result_slot->tts_xcnodeoid = slot->tts_xcnodeoid;
|
||||
#endif /* PGXC */
|
||||
if (is_done != ExprEndResult) {
|
||||
|
|
@ -211,15 +212,14 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct
|
|||
|
||||
/*
|
||||
* @hdfs
|
||||
* Optimize foreign scan by using informational constraint.
|
||||
* 使用信息约束优化外部扫描。
|
||||
*/
|
||||
if (IsA(node->ps.plan, ForeignScan)) {
|
||||
ForeignScan* foreign_scan = (ForeignScan*)(node->ps.plan);
|
||||
if (foreign_scan->scan.scan_qual_optimized) {
|
||||
/*
|
||||
* If we find a suitable tuple, set is_scan_end value is true.
|
||||
* It means that we do not find suitable tuple in the next iteration,
|
||||
* the iteration is over.
|
||||
* 如果我们找到一个合适的元组,那么set is_scan_end值为true。
|
||||
* 这意味着我们在下一次迭代中没有找到合适的元组,迭代结束了。
|
||||
*/
|
||||
node->is_scan_end = true;
|
||||
}
|
||||
|
|
@ -228,21 +228,20 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct
|
|||
}
|
||||
} else {
|
||||
/*
|
||||
* Optimize foreign scan by using informational constraint.
|
||||
* 使用信息约束优化外部扫描。
|
||||
*/
|
||||
if (IsA(node->ps.plan, ForeignScan)) {
|
||||
ForeignScan* foreign_scan = (ForeignScan*)(node->ps.plan);
|
||||
if (foreign_scan->scan.scan_qual_optimized) {
|
||||
/*
|
||||
* If we find a suitable tuple, set is_scan_end value is true.
|
||||
* It means that we do not find suitable tuple in the next iteration,
|
||||
* the iteration is over.
|
||||
* 如果我们找到一个合适的元组,那么set is_scan_end值为true。
|
||||
* 这意味着我们在下一次迭代中没有找到合适的元组,迭代结束了。
|
||||
*/
|
||||
node->is_scan_end = true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Here, we aren't projecting, so just return scan tuple.
|
||||
* 在这里,我们不投影,所以只返回扫描元组。
|
||||
*/
|
||||
return slot;
|
||||
}
|
||||
|
|
@ -250,7 +249,7 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct
|
|||
InstrCountFiltered1(node, 1);
|
||||
|
||||
/*
|
||||
* Tuple fails qual, so free per-tuple memory and try again.
|
||||
* 元组无法通过qual,请释放每个元组的内存,然后重试。
|
||||
*/
|
||||
ResetExprContext(econtext);
|
||||
}
|
||||
|
|
@ -258,23 +257,21 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct
|
|||
|
||||
/*
|
||||
* ExecAssignScanProjectionInfo
|
||||
* Set up projection info for a scan node, if necessary.
|
||||
* 如有必要,为扫描节点设置投影信息。
|
||||
*
|
||||
* We can avoid a projection step if the requested tlist exactly matches
|
||||
* the underlying tuple type. If so, we just set ps_ProjInfo to NULL.
|
||||
* Note that this case occurs not only for simple "SELECT * FROM ...", but
|
||||
* also in most cases where there are joins or other processing nodes above
|
||||
* the scan node, because the planner will preferentially generate a matching
|
||||
* tlist.
|
||||
* 如果请求的tlist与底层元组类型完全匹配,我们可以避免投影步骤。
|
||||
* 如果是,我们只需将ps_ProjegInfo设置为NULL。
|
||||
* 请注意,这种情况不仅发生在简单的“SELECT*FROM…”中,而且发生在扫描节点上方有联接或其他处理节点的大多数情况下,
|
||||
* 因为计划器将优先生成匹配的tlist。
|
||||
*
|
||||
* ExecAssignScanType must have been called already.
|
||||
* 必须已调用ExecAssignScanType
|
||||
*/
|
||||
void ExecAssignScanProjectionInfo(ScanState* node)
|
||||
{
|
||||
Scan* scan = (Scan*)node->ps.plan;
|
||||
Index var_no;
|
||||
|
||||
/* Vars in an index-only scan's tlist should be INDEX_VAR */
|
||||
/* 仅索引扫描的tlist中的变量应为index_VAR */
|
||||
if (IsA(scan, IndexOnlyScan))
|
||||
var_no = INDEX_VAR;
|
||||
else
|
||||
|
|
@ -285,15 +282,35 @@ void ExecAssignScanProjectionInfo(ScanState* node)
|
|||
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 ...".
|
||||
* 如上所述,但调用者可以在tlist中的Vars中指定varno。
|
||||
* ExecInitExtensiblePlan调用此函数来初始化投影信息。
|
||||
* 通常,调用者提供一个描述扫描元组的目标列表,因此我们可以通过将ps_ProjegInfo设置为NULL来避免投影步骤。例如“SELECT*FROM…”。
|
||||
*/
|
||||
void ExecAssignScanProjectionInfoWithVarno(ScanState* node, Index var_no)
|
||||
/* 函数ExecAssignScanProjectionInfoWithVarno将ScanState对象和Index变量号作为参数。它用于为具有特定变量编号的扫描节点分配投影信息。
|
||||
以下是该功能的逐步分解:
|
||||
它采用ScanState对象node,表示执行计划中扫描操作的状态。
|
||||
它采用var_no变量号,用于标识需要为其分配投影信息的特定变量。
|
||||
函数首先检查var_no是否有效并且是否在可用变量的范围内。
|
||||
如果var_no有效,则函数从预定义的数据结构或查找表中检索与该变量编号相关联的投影信息。
|
||||
投影信息通常包括诸如目标列表、目标表达式和处理扫描操作所需的其他相关信息之类的细节。
|
||||
一旦检索到投影信息,就会将其分配给节点对象,更新其内部状态以反映指定变量的投影信息。
|
||||
总体而言,函数ExecAssignScanProjectionInfoWithVarno负责为具有特定变量号的扫描节点分配投影信息,使扫描操作能够正确处理和检索所需数据。
|
||||
*/
|
||||
|
||||
{
|
||||
Scan* scan = (Scan*)node->ps.plan;
|
||||
|
||||
|
|
@ -310,46 +327,42 @@ bool tlist_matches_tupdesc(PlanState* ps, List* tlist, Index var_no, TupleDesc t
|
|||
bool has_oid = false;
|
||||
ListCell* tlist_item = list_head(tlist);
|
||||
|
||||
/* Check the tlist attributes */
|
||||
/* 检查tlist属性 */
|
||||
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 */
|
||||
return false; /* tlist太短 */
|
||||
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 */
|
||||
return false; /* tlist项不是Var */
|
||||
/* 如果这些断言失败,计划者就会搞砸 */
|
||||
Assert(var->varno == var_no);
|
||||
Assert(var->varlevelsup == 0);
|
||||
if (var->varattno != attr_no)
|
||||
return false; /* out of order */
|
||||
return false; /* 发生故障 */
|
||||
if (att_tup->attisdropped)
|
||||
return false; /* table contains dropped columns */
|
||||
return false; /* 表包含删除的列 */
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* 注意:通常Var的类型应该与元组完全匹配,但在涉及具有不同类型mod的列的并集的情况下,
|
||||
* Var可能来自并集之上,因此具有类型mod-1。这是一种合理的情况,因为Var仍然描述列,
|
||||
* 只是不像tudesc那样准确。我们可以更改计划来防止它,但它会插入投影步骤,
|
||||
* 只是为了从特定的typmod转换为typmod-1,这很愚蠢。
|
||||
*/
|
||||
if (var->vartype != att_tup->atttypid || (var->vartypmod != att_tup->atttypmod && var->vartypmod != -1))
|
||||
return false; /* type mismatch */
|
||||
return false; /* 类型不匹配 */
|
||||
|
||||
tlist_item = lnext(tlist_item);
|
||||
}
|
||||
|
||||
if (tlist_item != NULL)
|
||||
return false; /* tlist too long */
|
||||
return false; /* tlist 列表太长 */
|
||||
|
||||
/*
|
||||
* If the plan context requires a particular hasoid setting, then that has
|
||||
* to match, too.
|
||||
* 如果计划上下文需要特定的hasoid设置,
|
||||
* 那么它也必须匹配。
|
||||
*/
|
||||
if (ExecContextForcesOids(ps, &has_oid) && has_oid != tup_desc->tdhasoid)
|
||||
return false;
|
||||
|
|
@ -360,17 +373,16 @@ bool tlist_matches_tupdesc(PlanState* ps, List* tlist, Index var_no, TupleDesc t
|
|||
/*
|
||||
* ExecScanReScan
|
||||
*
|
||||
* This must be called within the ReScan function of any plan node type
|
||||
* that uses ExecScan().
|
||||
* 这必须在使用ExecScan()的任何计划节点类型的ReScan函数中调用。
|
||||
*/
|
||||
void ExecScanReScan(ScanState* node)
|
||||
{
|
||||
EState* estate = node->ps.state;
|
||||
|
||||
/* Stop projecting any tuples from SRFs in the targetlist */
|
||||
/* 停止从目标列表中的SRF投影任何元组 */
|
||||
node->ps.ps_TupFromTlist = false;
|
||||
|
||||
/* Rescan EvalPlanQual tuple if we're inside an EvalPlanQual recheck */
|
||||
/* 如果我们在EvalPlanQual复查中,则重新扫描EvalPlanQual元组 */
|
||||
if (estate->es_epqScanDone != NULL) {
|
||||
Index scan_rel_id = ((Scan*)node->ps.plan)->scanrelid;
|
||||
|
||||
|
|
@ -379,3 +391,14 @@ void ExecScanReScan(ScanState* node)
|
|||
estate->es_epqScanDone[scan_rel_id - 1] = false;
|
||||
}
|
||||
}
|
||||
/* 函数ExecScanReScan将ScanState对象作为参数,用于重置扫描操作的状态,以便重新扫描数据。
|
||||
以下是该功能的逐步分解:
|
||||
它采用ScanState对象node,表示执行计划中扫描操作的状态。
|
||||
该函数首先检查扫描操作是否已初始化,以及执行重新扫描是否安全。
|
||||
这样做通常是为了确保必要的资源可用,并且扫描操作处于重新扫描的有效状态。
|
||||
如果重新扫描是安全的,该功能将执行必要的步骤来重置扫描操作。这可能涉及重置内部指针、重新初始化变量或释放在上次扫描期间分配的任何资源。
|
||||
一旦重置了扫描操作,就可以从头开始重新执行,从而允许对底层数据源进行新的扫描。
|
||||
这在数据已被修改或出于任何原因需要重复扫描操作的情况下很有用。
|
||||
总体而言,ExecScanReScan功能提供了一种重置扫描操作状态的机制,使其能够从头开始重新执行。
|
||||
这使得处理和分析数据具有灵活性,尤其是在需要重复扫描操作或基础数据发生变化的情况下。
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
/* -------------------------------------------------------------------------
|
||||
*
|
||||
* execTuples.cpp
|
||||
* Routines dealing with TupleTableSlots. These are used for resource
|
||||
* management associated with tuples (eg, releasing buffer pins for
|
||||
* tuples in disk buffers, or freeing the memory occupied by transient
|
||||
* tuples). Slots also provide access abstraction that lets us implement
|
||||
* "virtual" tuples to reduce data-copying overhead.
|
||||
* 处理TupleTableSlots的例程。这些用于与元组相关的资源管理
|
||||
*(例如,释放磁盘缓冲区中元组的缓冲引脚,或释放传输元组占用的内存)。
|
||||
* 插槽还提供访问抽象,使我们能够实现“虚拟”元组,以减少数据复制开销。
|
||||
*
|
||||
* Routines dealing with the type information for tuples. Currently,
|
||||
* the type information for a tuple is an array of FormData_pg_attribute.
|
||||
* This information is needed by routines manipulating tuples
|
||||
* (getattribute, formtuple, etc.).
|
||||
* 处理元组的类型信息的例程。
|
||||
* 目前,元组的类型信息是FormData_pg_attribute的数组。
|
||||
* 处理元组(getattribute、formtuple等)的例程需要这些信息。
|
||||
*
|
||||
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
||||
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
* 部分版权所有(c)2020华为技术有限公司有限公司。
|
||||
* 部分版权所有(c)1996-2012,PostgreSQL全球发展集团
|
||||
* 部分版权所有(c)1994,加州大学董事会
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
|
|
@ -24,27 +21,27 @@
|
|||
* INTERFACE ROUTINES
|
||||
*
|
||||
* SLOT CREATION/DESTRUCTION
|
||||
* MakeTupleTableSlot - create an empty slot
|
||||
* ExecAllocTableSlot - create a slot within a tuple table
|
||||
* ExecResetTupleTable - clear and optionally delete a tuple table
|
||||
* MakeSingleTupleTableSlot - make a standalone slot, set its descriptor
|
||||
* ExecDropSingleTupleTableSlot - destroy a standalone slot
|
||||
* MakeTupleTableSlot - 创建一个空插槽
|
||||
* ExecAllocTableSlot - 在元组表中创建槽
|
||||
* ExecResetTupleTable - 清除并可选择删除元组表
|
||||
* MakeSingleTupleTableSlot - 制作一个独立插槽,设置其描述符
|
||||
* ExecDropSingleTupleTableSlot - 销毁独立插槽
|
||||
*
|
||||
* SLOT ACCESSORS
|
||||
* ExecSetSlotDescriptor - set a slot's tuple descriptor
|
||||
* ExecStoreTuple - store a physical tuple in the slot
|
||||
* ExecStoreMinimalTuple - store a minimal physical tuple in the slot
|
||||
* ExecClearTuple - clear contents of a slot
|
||||
* ExecStoreVirtualTuple - mark slot as containing a virtual tuple
|
||||
* ExecCopySlotTuple - build a physical tuple from a slot
|
||||
* ExecCopySlotMinimalTuple - build a minimal physical tuple from a slot
|
||||
* ExecMaterializeSlot - convert virtual to physical storage
|
||||
* ExecCopySlot - copy one slot's contents to another
|
||||
* ExecSetSlotDescriptor - 设置槽的元组描述符
|
||||
* ExecStoreTuple - 在插槽中存储物理元组
|
||||
* ExecStoreMinimalTuple - 在插槽中存储最小物理元组
|
||||
* ExecClearTuple - 清除槽中的内容
|
||||
* ExecStoreVirtualTuple - 将slot标记为包含虚拟元组
|
||||
* ExecCopySlotTuple - 从插槽构建物理元组
|
||||
* ExecCopySlotMinimalTuple - 从插槽构建最小物理元组
|
||||
* ExecMaterializeSlot - 将虚拟存储转换为物理存储
|
||||
* ExecCopySlot - 将一个插槽的内容复制到另一个插槽
|
||||
*
|
||||
* CONVENIENCE INITIALIZATION ROUTINES
|
||||
* ExecInitResultTupleSlot \ convenience routines to initialize
|
||||
* ExecInitScanTupleSlot \ the various tuple slots for nodes
|
||||
* ExecInitExtraTupleSlot / which store copies of tuples.
|
||||
* CONVENIENCE INITIALIZATION ROUTINES (便利初始化例程)
|
||||
* ExecInitResultTupleSlot \ convenience routines to initialize (初始化的便利例程)
|
||||
* ExecInitScanTupleSlot \ the various tuple slots for nodes (节点的各种元组槽)
|
||||
* ExecInitExtraTupleSlot / which store copies of tuples. (其存储元组的副本)
|
||||
* ExecInitNullTupleSlot /
|
||||
*
|
||||
* Routines that probably belong somewhere else:
|
||||
|
|
@ -103,7 +100,7 @@
|
|||
static TupleDesc ExecTypeFromTLInternal(List* target_list, bool has_oid, bool skip_junk, bool mark_dropped = false, TableAmType tam = TAM_HEAP);
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* tuple table create/delete functions
|
||||
* tuple table create/delete functions (元组表创建/删除函数)
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
/* --------------------------------
|
||||
|
|
@ -113,6 +110,10 @@ static TupleDesc ExecTypeFromTLInternal(List* target_list, bool has_oid, bool sk
|
|||
* --------------------------------
|
||||
*/
|
||||
TupleTableSlot* MakeTupleTableSlot(bool has_tuple_mcxt, TableAmType tupslotTableAm)
|
||||
/* 它创建一个新的TupleTableSlot对象并返回一个指向它的指针。
|
||||
“has_tuple_mcxt”参数是一个布尔值,指示插槽是否应该有一个用于存储元组数据的内存上下文。
|
||||
“tupslotTableAm”参数是一个可选参数,用于指定插槽的表访问方法。如果未指定,则默认为TAM_HEAP。
|
||||
*/
|
||||
{
|
||||
TupleTableSlot* slot = makeNode(TupleTableSlot);
|
||||
Assert(tupslotTableAm == TAM_HEAP || tupslotTableAm == TAM_USTORE);
|
||||
|
|
@ -149,7 +150,7 @@ TupleTableSlot* MakeTupleTableSlot(bool has_tuple_mcxt, TableAmType tupslotTable
|
|||
/* --------------------------------
|
||||
* ExecAllocTableSlot
|
||||
*
|
||||
* Create a tuple table slot within a tuple table (which is just a List).
|
||||
* Create a tuple table slot within a tuple table (which is just a List). //在元组表(它只是一个列表)中创建一个元组表槽。
|
||||
* --------------------------------
|
||||
*/
|
||||
TupleTableSlot* ExecAllocTableSlot(List** tuple_table, TableAmType tupslotTableAm)
|
||||
|
|
@ -164,32 +165,37 @@ TupleTableSlot* ExecAllocTableSlot(List** tuple_table, TableAmType tupslotTableA
|
|||
|
||||
return slot;
|
||||
}
|
||||
/*它将指向TupleTableSlot对象列表的指针和TableAmType参数作为输入。它返回一个指向新分配的TupleTableSlot对象的指针。
|
||||
该函数首先调用MakeTupleTableSlot()函数来创建一个新的TupleTableSlot对象,并将其分配给“slot”变量。
|
||||
然后,它使用lappend()函数将新创建的插槽添加到“tuple_table”参数指向的列表末尾。
|
||||
之后,它将插槽的“tts_tupslotTableAm”字段设置为“tupslotTableAm”参数的值。
|
||||
最后,它返回一个指向新创建的TupleTableSlot对象的指针。
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecResetTupleTable
|
||||
*
|
||||
* This releases any resources (buffer pins, tupdesc refcounts)
|
||||
* held by the tuple table, and optionally releases the memory
|
||||
* occupied by the tuple table data structure.
|
||||
* It is expected that this routine be called by EndPlan().
|
||||
* 这释放了元组表所拥有的任何资源(缓冲区引脚、元组引用计数),
|
||||
* 并有选择地释放元组表数据结构所占用的内存。
|
||||
* 这个例程应该由EndPlan()调用。
|
||||
* --------------------------------
|
||||
*/
|
||||
void ExecResetTupleTable(List* tuple_table, /* tuple table */
|
||||
bool should_free) /* true if we should free memory */
|
||||
bool should_free) /* true ,如果我们应该释放内存 */
|
||||
{
|
||||
ListCell* lc = NULL;
|
||||
|
||||
foreach (lc, tuple_table) {
|
||||
TupleTableSlot* slot = (TupleTableSlot*)lfirst(lc);
|
||||
|
||||
/* Always release resources and reset the slot to empty */
|
||||
/* 始终释放资源并将插槽重置为空*/
|
||||
(void)ExecClearTuple(slot);
|
||||
if (slot->tts_tupleDescriptor) {
|
||||
ReleaseTupleDesc(slot->tts_tupleDescriptor);
|
||||
slot->tts_tupleDescriptor = NULL;
|
||||
}
|
||||
|
||||
/* If shouldFree, release memory occupied by the slot itself */
|
||||
/* 如果应该释放,释放插槽本身占用的内存 */
|
||||
if (should_free) {
|
||||
if (slot->tts_values)
|
||||
pfree_ext(slot->tts_values);
|
||||
|
|
@ -202,13 +208,33 @@ void ExecResetTupleTable(List* tuple_table, /* tuple table */
|
|||
}
|
||||
}
|
||||
|
||||
/* If shouldFree, release the list structure */
|
||||
/* 如果应该释放,则释放列表结构 */
|
||||
if (should_free) {
|
||||
list_free_ext(tuple_table);
|
||||
}
|
||||
}
|
||||
/* 这是一个用于重置元组表的函数。
|
||||
* 它接受一个元组表(tuple_table)和一个布尔值(should_free),用于指示是否应该释放内存。
|
||||
* 函数首先遍历元组表中的每个元素,每个元素都是一个TupleTableSlot类型的指针。
|
||||
* 然后,函数调用ExecClearTuple函数来释放资源并将槽(slot)重置为空。
|
||||
* 接下来,如果槽(slot)的tts_tupleDescriptor字段不为空,函数会调用ReleaseTupleDesc函数释放该字段指向的TupleDesc结构体。
|
||||
* 然后,如果should_free为真,函数会释放槽(slot)本身占用的内存。
|
||||
* 它会释放tts_values、tts_isnull和tts_lobPointers字段指向的内存,并删除tts_per_tuple_mcxt字段指向的内存上下文。
|
||||
* 最后,函数会释放槽(slot)本身占用的内存。
|
||||
* 最后,如果should_free为真,函数会释放元组表的列表结构。
|
||||
*/
|
||||
|
||||
TupleTableSlot* ExecMakeTupleSlot(Tuple tuple, TableScanDesc tableScan, TupleTableSlot* slot, TableAmType tableAm)
|
||||
/* 这是一个用于创建TupleTableSlot的函数。它接受一个Tuple类型的参数tuple,一个TableScanDesc类型的参数tableScan,
|
||||
一个TupleTableSlot类型的参数slot,以及一个TableAmType类型的参数tableAm。
|
||||
|
||||
函数的作用是创建一个新的TupleTableSlot,并将传入的参数赋值给相应的字段。具体的实现可能包括以下步骤:
|
||||
创建一个新的TupleTableSlot对象,并将其赋值给参数slot。
|
||||
将参数tuple赋值给slot的tts_tuple字段,表示该槽持有的元组。
|
||||
将参数tableScan赋值给slot的tts_tableScan字段,表示该槽所属的表扫描描述符。
|
||||
将参数tableAm赋值给slot的tts_tableAm字段,表示该槽所属的表访问方法类型。
|
||||
函数最后会返回创建的TupleTableSlot对象。
|
||||
*/
|
||||
{
|
||||
if (unlikely(RELATION_CREATE_BUCKET(tableScan->rs_rd))) {
|
||||
tableScan = ((HBktTblScanDesc)tableScan)->currBktScan;
|
||||
|
|
@ -225,14 +251,24 @@ TupleTableSlot* ExecMakeTupleSlot(Tuple tuple, TableScanDesc tableScan, TupleTab
|
|||
|
||||
return ExecClearTuple(slot);
|
||||
}
|
||||
/* 这段代码是一个用于创建TupleTableSlot的函数。它接受一个Tuple类型的参数tuple,
|
||||
一个TableScanDesc类型的参数tableScan,一个TupleTableSlot类型的参数slot,以及一个TableAmType类型的参数tableAm。
|
||||
|
||||
函数的逻辑如下:
|
||||
首先,检查是否需要创建哈希桶扫描描述符。如果是哈希桶扫描,则将tableScan指向当前桶扫描描述符。
|
||||
然后,检查传入的tuple是否为NULL。如果不为NULL,则执行以下步骤:
|
||||
确保tableScan不为NULL。
|
||||
将tableAm赋值给slot的tts_tupslotTableAm字段,表示该槽所属的表访问方法类型。
|
||||
调用ExecStoreTuple函数,将tuple存储到slot中,使用tableScan->rs_cbuf指定的缓冲区,不释放该指针。
|
||||
返回存储后的TupleTableSlot对象。
|
||||
如果tuple为NULL,则调用ExecClearTuple函数,将slot清空,并返回清空后的TupleTableSlot对象。
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* MakeSingleTupleTableSlot
|
||||
*
|
||||
* This is a convenience routine for operations that need a
|
||||
* standalone TupleTableSlot not gotten from the main executor
|
||||
* tuple table. It makes a single slot and initializes it
|
||||
* to use the given tuple descriptor.
|
||||
* 这是一个方便的例程,用于需要独立的TupleTableSlot而不是从主执行器元组表中获得的操作。
|
||||
* 它生成一个槽,并对其进行初始化以使用给定的元组描述符。
|
||||
* --------------------------------
|
||||
*/
|
||||
TupleTableSlot* MakeSingleTupleTableSlot(TupleDesc tup_desc, bool allocSlotCxt, TableAmType tupslotTableAm)
|
||||
|
|
@ -245,13 +281,13 @@ TupleTableSlot* MakeSingleTupleTableSlot(TupleDesc tup_desc, bool allocSlotCxt,
|
|||
/* --------------------------------
|
||||
* ExecDropSingleTupleTableSlot
|
||||
*
|
||||
* Release a TupleTableSlot made with MakeSingleTupleTableSlot.
|
||||
* DON'T use this on a slot that's part of a tuple table list!
|
||||
* Release a TupleTableSlot made with MakeSingleTupleTableSlot.(释放一个用MakeSingleTupleTableSlot制作的TupleTableSlot)
|
||||
* DON'T use this on a slot that's part of a tuple table list! (不要在元组表列表中的插槽中使用此选项)
|
||||
* --------------------------------
|
||||
*/
|
||||
void ExecDropSingleTupleTableSlot(TupleTableSlot* slot)
|
||||
{
|
||||
/* This should match ExecResetTupleTable's processing of one slot */
|
||||
/* This should match ExecResetTupleTable's processing of one slot(这应该与ExecResetTupleTable对一个插槽的处理相匹配) */
|
||||
(void)ExecClearTuple(slot);
|
||||
if (slot->tts_tupleDescriptor != NULL) {
|
||||
ReleaseTupleDesc(slot->tts_tupleDescriptor);
|
||||
|
|
@ -272,6 +308,16 @@ void ExecDropSingleTupleTableSlot(TupleTableSlot* slot)
|
|||
}
|
||||
pfree_ext(slot);
|
||||
}
|
||||
/* 这段代码是用于释放一个单独的TupleTableSlot的资源。下面是对代码的逐行解释:
|
||||
(void)ExecClearTuple(slot);:清除slot中的tuple数据。
|
||||
if (slot->tts_tupleDescriptor != NULL) { ReleaseTupleDesc(slot->tts_tupleDescriptor); }:释放slot中的tuple描述符。
|
||||
if (slot->tts_values != NULL) { pfree_ext(slot->tts_values); }:释放slot中的tuple值数组。
|
||||
if (slot->tts_isnull != NULL) { pfree_ext(slot->tts_isnull); }:释放slot中的null标志数组。
|
||||
pfree_ext(slot->tts_lobPointers);:释放slot中的LOB指针。
|
||||
if (slot->tts_per_tuple_mcxt != NULL) { MemoryContextDelete(slot->tts_per_tuple_mcxt); }:删除slot中的内存上下文。
|
||||
pfree_ext(slot);:释放slot本身的内存。
|
||||
这段代码的作用是完全释放一个TupleTableSlot所占用的资源,确保没有内存泄漏。
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* tuple table slot accessor functions
|
||||
|
|
@ -285,23 +331,26 @@ void ExecDropSingleTupleTableSlot(TupleTableSlot* slot)
|
|||
* at least equal to the slot's. If it is a reference-counted descriptor
|
||||
* then the reference count is incremented for as long as the slot holds
|
||||
* a reference.
|
||||
* ( 此函数用于设置关联的元组描述符与插槽的元组。
|
||||
* 传递的描述符的寿命必须至少等于插槽的寿命。
|
||||
* 如果它是一个引用计数的描述符,那么只要插槽中有引用,引用计数就会递增。)
|
||||
* --------------------------------
|
||||
*/
|
||||
void ExecSetSlotDescriptor(TupleTableSlot* slot, /* slot to change */
|
||||
TupleDesc tup_desc) /* new tuple descriptor */
|
||||
void ExecSetSlotDescriptor(TupleTableSlot* slot, /* 要更改的插槽 */
|
||||
TupleDesc tup_desc) /* 新元组描述符 */
|
||||
{
|
||||
/* For safety, make sure slot is empty before changing it */
|
||||
/*为了安全起见,在更换插槽之前,请确保插槽为空*/
|
||||
(void)ExecClearTuple(slot);
|
||||
|
||||
/*
|
||||
* Release any old descriptor. Also release old Datum/isnull arrays if
|
||||
* present (we don't bother to check if they could be re-used).
|
||||
* 释放任何旧的描述符。如果存在,也释放旧的Datum/isull数组
|
||||
*(我们不必检查它们是否可以重复使用)。
|
||||
*/
|
||||
if (slot->tts_tupleDescriptor != NULL) {
|
||||
ReleaseTupleDesc(slot->tts_tupleDescriptor);
|
||||
}
|
||||
#ifdef PGXC
|
||||
/* XXX there in no routine to release AttInMetadata instance */
|
||||
/* XXX there in no routine to release AttInMetadata instance(XXX没有发布AttInMetadata实例的例程) */
|
||||
if (slot->tts_attinmeta != NULL) {
|
||||
slot->tts_attinmeta = NULL;
|
||||
}
|
||||
|
|
@ -315,35 +364,47 @@ void ExecSetSlotDescriptor(TupleTableSlot* slot, /* slot to change */
|
|||
}
|
||||
pfree_ext(slot->tts_lobPointers);
|
||||
/*
|
||||
* Install the new descriptor; if it's refcounted, bump its refcount.
|
||||
* 安装新的描述符;如果它被重新计数,就增加它的重新计数。
|
||||
*/
|
||||
slot->tts_tupleDescriptor = tup_desc;
|
||||
PinTupleDesc(tup_desc);
|
||||
|
||||
/*
|
||||
* Allocate Datum/isnull arrays of the appropriate size. These must have
|
||||
* the same lifetime as the slot, so allocate in the slot's own context.
|
||||
* 分配适当大小的所有阵列。它们必须与插槽具有相同的生存期,因此在插槽自己的上下文中进行分配。
|
||||
*/
|
||||
slot->tts_values = (Datum*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(Datum));
|
||||
slot->tts_isnull = (bool*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(bool));
|
||||
slot->tts_lobPointers = (Datum*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(Datum));
|
||||
}
|
||||
/*这段代码用于设置TupleTableSlot的描述符(descriptor)。下面是对代码的逐行解释:
|
||||
(void)ExecClearTuple(slot);:清除slot中的tuple数据,以确保slot为空。
|
||||
if (slot->tts_tupleDescriptor != NULL) { ReleaseTupleDesc(slot->tts_tupleDescriptor); }:释放slot中的旧的tuple描述符。
|
||||
#ifdef PGXC ... #endif:这部分代码是针对特定的条件编译,可能与特定的PostgreSQL扩展相关,我们暂时不考虑它的作用。
|
||||
if (slot->tts_values != NULL) { pfree_ext(slot->tts_values); }:释放slot中的旧的tuple值数组。
|
||||
if (slot->tts_isnull != NULL) { pfree_ext(slot->tts_isnull); }:释放slot中的旧的null标志数组。
|
||||
pfree_ext(slot->tts_lobPointers);:释放slot中的旧的LOB指针。
|
||||
slot->tts_tupleDescriptor = tup_desc;:将新的tuple描述符赋值给slot。
|
||||
PinTupleDesc(tup_desc);:增加tuple描述符的引用计数。
|
||||
slot->tts_values = (Datum*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(Datum));:在slot的内存上下文中分配新的tuple值数组。
|
||||
slot->tts_isnull = (bool*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(bool));:在slot的内存上下文中分配新的null标志数组。
|
||||
slot->tts_lobPointers = (Datum*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(Datum));:在slot的内存上下文中分配新的LOB指针数组。
|
||||
这段代码的作用是设置一个TupleTableSlot的描述符,并释放旧的描述符和相关的资源。同时,为新的描述符分配新的内存空间,并将其赋值给slot。
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecStoreTuple
|
||||
*
|
||||
* This function is used to store a physical tuple into a specified
|
||||
* slot in the tuple table.
|
||||
* slot in the tuple table.(此函数用于将物理元组存储到元组表中的指定槽中)
|
||||
*
|
||||
* tuple: tuple to store
|
||||
* slot: slot to store it in
|
||||
* buffer: disk buffer if tuple is in a disk page, else InvalidBuffer
|
||||
* tuple: tuple to store(要存储的元组)
|
||||
* slot: slot to store it in(用于存储的插槽)
|
||||
* buffer: disk buffer if tuple is in a disk page, else InvalidBuffer(磁盘缓冲区如果元组在磁盘页中,则为InvalidBuffer)
|
||||
* shouldFree: true if ExecClearTuple should pfree_ext() the tuple
|
||||
* when done with it
|
||||
* when done with it 如果ExecClearTuple在处理完元组后应该pfree_ext(),则为true
|
||||
*
|
||||
* If 'buffer' is not InvalidBuffer, the tuple table code acquires a pin
|
||||
* on the buffer which is held until the slot is cleared, so that the tuple
|
||||
* won't go away on us.
|
||||
* 如果“buffer”不是InvalidBuffer,元组表代码将获取缓冲区上的一个pin,
|
||||
* 该pin将一直保留到插槽被清除,这样元组就不会在我们身上消失。
|
||||
*
|
||||
* shouldFree is normally set 'true' for tuples constructed on-the-fly.
|
||||
* It must always be 'false' for tuples that are stored in disk pages,
|
||||
|
|
@ -370,7 +431,7 @@ void ExecSetSlotDescriptor(TupleTableSlot* slot, /* slot to change */
|
|||
TupleTableSlot* ExecStoreTuple(Tuple tuple, TupleTableSlot* slot, Buffer buffer, bool should_free)
|
||||
{
|
||||
/*
|
||||
* sanity checks
|
||||
* sanity checks (健全性检查)
|
||||
*/
|
||||
Assert(tuple != NULL);
|
||||
Assert(slot != NULL);
|
||||
|
|
@ -387,49 +448,72 @@ TupleTableSlot* ExecStoreTuple(Tuple tuple, TupleTableSlot* slot, Buffer buffer,
|
|||
|
||||
return slot;
|
||||
}
|
||||
/*
|
||||
这段代码用于将一个Tuple存储到TupleTableSlot中。下面是对代码的逐行解释:
|
||||
Assert(tuple != NULL);:断言tuple不为空。
|
||||
Assert(slot != NULL);:断言slot不为空。
|
||||
Assert(slot->tts_tupleDescriptor != NULL);:断言slot的tuple描述符不为空。
|
||||
HeapTuple htup = (HeapTuple)tuple;:将tuple强制转换为HeapTuple类型,并赋值给htup。
|
||||
if (slot->tts_tupslotTableAm == TAM_USTORE && htup->tupTableType == HEAP_TUPLE):如果slot的存储类型是UStore,并且htup的表类型是Heap Tuple,则执行以下操作:
|
||||
tuple = (Tuple)HeapToUHeap(slot->tts_tupleDescriptor, (HeapTuple)tuple);:将Heap Tuple转换为UHeap Tuple。
|
||||
else if (slot->tts_tupslotTableAm == TAM_HEAP && htup->tupTableType == UHEAP_TUPLE):如果slot的存储类型是Heap,并且htup的表类型是UHeap Tuple,则执行以下操作:
|
||||
tuple = (Tuple)UHeapToHeap(slot->tts_tupleDescriptor, (UHeapTuple)tuple);:将UHeap Tuple转换为Heap Tuple。
|
||||
tableam_tslot_store_tuple(tuple, slot, buffer, should_free, false);:调用tableam_tslot_store_tuple函数将tuple存储到slot中。
|
||||
return slot;:返回存储了tuple的slot。
|
||||
这段代码的作用是将一个Tuple存储到TupleTableSlot中,并根据需要进行表类型的转换。最后,返回存储了tuple的slot
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecStoreMinimalTuple
|
||||
*
|
||||
* Like ExecStoreTuple, but insert a "minimal" tuple into the slot.
|
||||
* Like ExecStoreTuple, but insert a "minimal" tuple into the slot. (与ExecStoreTuple类似,但在插槽中插入一个“最小”元组)
|
||||
*
|
||||
* No 'buffer' parameter since minimal tuples are never stored in relations.
|
||||
* No 'buffer' parameter since minimal tuples are never stored in relations. (没有“buffer”参数,因为最小元组从未存储在关系中)
|
||||
* --------------------------------
|
||||
*/
|
||||
TupleTableSlot* ExecStoreMinimalTuple(MinimalTuple mtup, TupleTableSlot* slot, bool should_free)
|
||||
{
|
||||
/*
|
||||
* sanity checks
|
||||
*健全性检查
|
||||
*/
|
||||
Assert(mtup != NULL);
|
||||
Assert(slot != NULL);
|
||||
Assert(slot->tts_tupleDescriptor != NULL);
|
||||
|
||||
/*
|
||||
* store the minimal tuple in the slot.
|
||||
*将最小元组存储在槽中。
|
||||
*/
|
||||
tableam_tslot_store_minimal_tuple(mtup, slot, should_free);
|
||||
|
||||
return slot;
|
||||
}
|
||||
/*
|
||||
这段代码用于将一个MinimalTuple存储到TupleTableSlot中。下面是对代码的逐行解释:
|
||||
Assert(mtup != NULL);:断言MinimalTuple不为空。
|
||||
Assert(slot != NULL);:断言slot不为空。
|
||||
Assert(slot->tts_tupleDescriptor != NULL);:断言slot的tuple描述符不为空。
|
||||
tableam_tslot_store_minimal_tuple(mtup, slot, should_free);:调用tableam_tslot_store_minimal_tuple函数将MinimalTuple存储到slot中。
|
||||
return slot;:返回存储了MinimalTuple的slot。
|
||||
这段代码的作用是将一个MinimalTuple存储到TupleTableSlot中,并返回存储了MinimalTuple的slot。
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecClearTuple
|
||||
*
|
||||
* This function is used to clear out a slot in the tuple table.
|
||||
* This function is used to clear out a slot in the tuple table.(此函数用于清除元组表中的一个槽)
|
||||
*
|
||||
* NB: only the tuple is cleared, not the tuple descriptor (if any).
|
||||
* NB: only the tuple is cleared, not the tuple descriptor (if any). (只有元组被清除,而不是元组描述符(如果有的话))
|
||||
* --------------------------------
|
||||
*/
|
||||
TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* return: slot passed slot in which to store tuple */
|
||||
TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* return:slot-passed存储元组的slot*/
|
||||
{
|
||||
/*
|
||||
* sanity checks
|
||||
* 健全性检查
|
||||
*/
|
||||
Assert(slot != NULL);
|
||||
|
||||
/*
|
||||
* clear the physical tuple or minimal tuple if present via TableAm.
|
||||
* 通过TableAm清除物理元组或最小元组(如果存在)。
|
||||
*/
|
||||
if (slot->tts_shouldFree || slot->tts_shouldFreeMin) {
|
||||
Assert(slot->tts_tupleDescriptor != NULL);
|
||||
|
|
@ -437,7 +521,7 @@ TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* return: slot passed slot
|
|||
}
|
||||
|
||||
/*
|
||||
* tts_tuple may still be valid if tts_shouldFree is false, Original caller doesn't want this slot to free the tuple.
|
||||
*如果tts_shouldFree为false,则tts_tuple可能仍然有效,原始调用方不希望此槽释放元组。
|
||||
*/
|
||||
slot->tts_tuple = NULL;
|
||||
slot->tts_mintuple = NULL;
|
||||
|
|
@ -455,7 +539,7 @@ TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* return: slot passed slot
|
|||
#endif
|
||||
|
||||
/*
|
||||
* Drop the pin on the referenced buffer, if there is one.
|
||||
*将引脚放置在引用的缓冲区上(如果有)。
|
||||
*/
|
||||
if (BufferIsValid(slot->tts_buffer)) {
|
||||
ReleaseBuffer(slot->tts_buffer);
|
||||
|
|
@ -463,19 +547,42 @@ TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* return: slot passed slot
|
|||
slot->tts_buffer = InvalidBuffer;
|
||||
|
||||
/*
|
||||
* Mark it empty.
|
||||
* 标记为空。
|
||||
*/
|
||||
slot->tts_isempty = true;
|
||||
slot->tts_nvalid = 0;
|
||||
|
||||
// Row uncompression use slot->tts_per_tuple_mcxt in some case, So we need
|
||||
// reset memory context. This memory context is introduced by PGXC and it only used
|
||||
// in function 'slot_deform_datarow'. PGXC also do reset in function 'FetchTuple'.
|
||||
// So it is safe
|
||||
//
|
||||
//在某些情况下,行解压缩使用slot->tts_per_tuple_mcxt,
|
||||
//因此我们需要重置内存上下文。此内存上下文由PGXC引入,
|
||||
// 仅在函数“slot_form_datarow”中使用。PGXC也在函数“FetchTuple”中进行重置。
|
||||
// 所以它是安全的
|
||||
//
|
||||
ResetSlotPerTupleContext(slot);
|
||||
return slot;
|
||||
}
|
||||
/*
|
||||
这段代码用于清除TupleTableSlot中的tuple。下面是对代码的逐行解释:
|
||||
Assert(slot != NULL);:断言slot不为空。
|
||||
if (slot->tts_shouldFree || slot->tts_shouldFreeMin):如果slot中的tuple需要释放,则执行以下操作:
|
||||
Assert(slot->tts_tupleDescriptor != NULL);:断言slot的tuple描述符不为空。
|
||||
tableam_tslot_clear(slot);:通过TableAm清除物理tuple或最小tuple。
|
||||
slot->tts_tuple = NULL;:将slot中的tuple置为NULL。
|
||||
slot->tts_mintuple = NULL;:将slot中的最小tuple置为NULL。
|
||||
slot->tts_shouldFree = false;:将slot的tts_shouldFree标志置为false,表示不需要释放tuple。
|
||||
slot->tts_shouldFreeMin = false;:将slot的tts_shouldFreeMin标志置为false,表示不需要释放最小tuple。
|
||||
if (slot->tts_shouldFreeRow) { pfree_ext(slot->tts_dataRow); }:如果slot的tts_shouldFreeRow标志为true,则释放slot中的数据行。
|
||||
slot->tts_shouldFreeRow = false;:将slot的tts_shouldFreeRow标志置为false,表示不需要释放数据行。
|
||||
slot->tts_dataRow = NULL;:将slot中的数据行置为NULL。
|
||||
slot->tts_dataLen = -1;:将slot中的数据长度置为-1。
|
||||
slot->tts_xcnodeoid = 0;:将slot的tts_xcnodeoid置为0。
|
||||
if (BufferIsValid(slot->tts_buffer)) { ReleaseBuffer(slot->tts_buffer); }:如果slot中的buffer有效,则释放buffer。
|
||||
slot->tts_buffer = InvalidBuffer;:将slot的buffer置为无效。
|
||||
slot->tts_isempty = true;:将slot的isempty标志置为true,表示slot为空。
|
||||
slot->tts_nvalid = 0;:将slot的nvalid置为0,表示有效的tuple数量为0。
|
||||
ResetSlotPerTupleContext(slot);:重置slot的tts_per_tuple_mcxt内存上下文。
|
||||
return slot;:返回清空了tuple的slot。
|
||||
这段代码的作用是清除TupleTableSlot中的tuple,并将slot重置为空。同时,释放相关的资源,并重置相应的标志和计数。最后,返回清空了tuple的slot。
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecStoreVirtualTuple
|
||||
|
|
@ -501,13 +608,22 @@ TupleTableSlot* ExecStoreVirtualTuple(TupleTableSlot* slot)
|
|||
slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
|
||||
|
||||
if (slot->tts_tupslotTableAm != slot->tts_tupleDescriptor->tdTableAmType) {
|
||||
// XXX: Should tts_tupleDescriptor be cloned before changing its contents
|
||||
// as some time it can be direct reference to the rd_att in RelationData.
|
||||
// XXX: 如果tts_tupleDescriptor在更改其内容之前进行克隆,
|
||||
//则它可以直接引用RelationData中的rd_att。
|
||||
slot->tts_tupleDescriptor->tdTableAmType = slot->tts_tupslotTableAm;
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
/*
|
||||
函数ExecStoreVirtualTuple用于将虚拟元组存储在元组表槽中。
|
||||
该函数以TupleTableSlot指针作为输入,该指针表示元组表中存储虚拟元组的槽。
|
||||
该函数执行一些健全性检查,以确保输入槽有效且为空。它检查slot是否不为NULL,slot->tts_tupleDescriptor(存储在slot中的元组的描述符)是否为NULL,以及slot->tss_isempty是否为true。
|
||||
如果健全性检查通过,函数会将slot->tts_isempty设置为false,表示该slot不再为空。
|
||||
该函数还将slot->tts_nvalid设置为元组描述符(slot->ttleStupleDescriptor->natts)中的属性数,表示元组中的所有属性都有效。
|
||||
然后,该函数检查slot->tts_tupslotTableAm(该slot的表访问方法)是否与slot->ttleStupleDescriptor->tdTableAmType(元组描述符的表访问方式)不同。如果它们不同,它会更新slot->tts_tupleDescriptor- >tdTableAmType以匹配slot->ts_tupslotTableAm。
|
||||
最后,该函数返回输入槽指针,该指针现在包含虚拟元组.
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecStoreAllNullTuple
|
||||
|
|
@ -525,11 +641,11 @@ TupleTableSlot* ExecStoreAllNullTuple(TupleTableSlot* slot)
|
|||
Assert(slot != NULL);
|
||||
Assert(slot->tts_tupleDescriptor != NULL);
|
||||
|
||||
/* Clear any old contents */
|
||||
/* 清除所有旧内容 */
|
||||
(void)ExecClearTuple(slot);
|
||||
|
||||
/*
|
||||
* Fill all the columns of the virtual tuple with nulls
|
||||
* 用null填充虚拟元组的所有列
|
||||
*/
|
||||
errno_t rc = EOK;
|
||||
|
||||
|
|
@ -546,6 +662,16 @@ TupleTableSlot* ExecStoreAllNullTuple(TupleTableSlot* slot)
|
|||
|
||||
return ExecStoreVirtualTuple(slot);
|
||||
}
|
||||
/*
|
||||
函数ExecStoreAllNullTuple用于将具有所有null值的虚拟元组存储在元组表槽中。
|
||||
该函数以TupleTableSlot指针作为输入,该指针表示元组表中存储虚拟元组的槽。
|
||||
该函数执行一些健全性检查,以确保输入槽有效。它检查slot是否为NULL,以及slot->tts_tupleDescriptor(存储在slot中的元组的描述符)是否为NULL。
|
||||
然后,该函数调用ExecClearTuple来清除插槽中的任何现有内容。这样可以确保在存储新的虚拟元组之前插槽是空的。
|
||||
该函数使用memset_s函数用null值填充插槽的tts_values数组。它将tts_values数组设置为全零,表示元组中每个属性的空值。数组的大小计算为slot->tts_tupleDescriptor->natts*sizeof(Datum)。
|
||||
类似地,该函数使用memset_s用真值填充插槽的tts_isull数组。此数组指示元组中的每个属性是否为null。通过将所有值设置为true,我们表示所有属性都为null。
|
||||
最后,该函数返回使用输入槽调用ExecStoreVirtualTuple的结果。此函数负责将插槽标记为非空,并设置有效属性的数量。
|
||||
此函数准备了一个元组表槽,用于存储具有所有null值的虚拟元组。
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecCopySlotTuple
|
||||
|
|
@ -560,13 +686,21 @@ TupleTableSlot* ExecStoreAllNullTuple(TupleTableSlot* slot)
|
|||
HeapTuple ExecCopySlotTuple(TupleTableSlot* slot)
|
||||
{
|
||||
/*
|
||||
* sanity checks
|
||||
* sanity checks (健全性检查)
|
||||
*/
|
||||
Assert(slot != NULL);
|
||||
Assert(!slot->tts_isempty);
|
||||
|
||||
return tableam_tslot_copy_heap_tuple(slot);
|
||||
}
|
||||
/*
|
||||
函数ExecCopySlotTuple用于创建存储在元组表槽中的元组的副本。
|
||||
该函数以TupleTableSlot指针作为输入,该指针表示包含要复制的元组的槽。
|
||||
该函数执行一些健全性检查,以确保输入槽有效且不为空。它检查slot是否不为NULL,以及slot->tts_isempty是否为false。
|
||||
如果健全性检查通过,函数将调用tableam_tslot_copy_heap_tuple来创建存储在插槽中的元组的副本。此函数负责为新元组分配内存,并从原始元组复制数据。
|
||||
最后,函数将复制的元组作为HeapTuple对象返回。
|
||||
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecCopySlotMinimalTuple
|
||||
|
|
@ -585,6 +719,13 @@ MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot* slot, bool need_transform_
|
|||
|
||||
return tableam_tslot_copy_minimal_tuple(slot);
|
||||
}
|
||||
/*
|
||||
函数ExecCopySlotMinimalTuple用于创建存储在元组表槽中的最小元组的副本。
|
||||
该函数以TupleTableSlot指针作为输入,该指针表示包含要复制的最小元组的槽。
|
||||
该函数执行一些健全性检查,以确保输入槽有效且不为空。它检查slot是否不为NULL,以及slot->tts_isempty是否为false。
|
||||
如果健全性检查通过,该函数将调用tableam_tslot_copy_minimaltuple来创建存储在插槽中的最小元组的副本。此函数负责为新的最小元组分配内存,并从原始最小元组复制数据。
|
||||
最后,函数将复制的最小元组作为MinimalTuple对象返回。
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecFetchSlotTuple
|
||||
|
|
@ -611,6 +752,13 @@ HeapTuple ExecFetchSlotTuple(TupleTableSlot* slot)
|
|||
|
||||
return tableam_tslot_get_heap_tuple(slot);
|
||||
}
|
||||
/*
|
||||
函数ExecFetchSlotTuple用于从元组表槽中检索堆元组。
|
||||
该函数以TupleTableSlot指针作为输入,该指针表示包含要获取的元组的槽。
|
||||
该函数执行一些健全性检查,以确保输入槽有效且不为空。它检查slot是否不为NULL,以及slot->tts_isempty是否为false。
|
||||
如果健全性检查通过,函数将调用tableam_tslot_get_heap_tuple从插槽中检索堆元组。此函数负责返回存储在插槽中的实际堆元组数据。
|
||||
最后,该函数将获取的堆元组作为HeapTuple对象返回。
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecFetchSlotMinimalTuple
|
||||
|
|
@ -634,7 +782,13 @@ MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot* slot)
|
|||
|
||||
return tableam_tslot_get_minimal_tuple(slot);
|
||||
}
|
||||
|
||||
/*
|
||||
函数ExecFetchSlotMinimalTuple用于从元组表槽中检索最小元组。
|
||||
该函数以TupleTableSlot指针作为输入,该指针表示包含要获取的最小元组的槽。
|
||||
该函数执行健全性检查,以确保输入槽有效。它检查slot是否不为NULL。
|
||||
如果健全性检查通过,该函数将调用tableam_tslot_get_minimaltuple从插槽中检索最小元组。此函数负责返回存储在插槽中的实际最小元组数据。
|
||||
最后,函数将提取的最小元组作为MinimalTuple对象返回。
|
||||
*/
|
||||
/* --------------------------------
|
||||
* ExecFetchSlotTupleDatum
|
||||
* Fetch the slot's tuple as a composite-type Datum.
|
||||
|
|
@ -650,9 +804,9 @@ Datum ExecFetchSlotTupleDatum(TupleTableSlot* slot)
|
|||
HeapTupleHeader td;
|
||||
TupleDesc tup_desc;
|
||||
|
||||
/* Make sure we can scribble on the slot contents ... */
|
||||
/* Make sure we can scribble on the slot contents ... (确保我们可以在插槽内容上乱写) */
|
||||
tup = ExecMaterializeSlot(slot);
|
||||
/* ... and set up the composite-Datum header fields, in case not done */
|
||||
/* ... and set up the composite-Datum header fields, in case not done(并设置复合基准标题字段,如果未完成) */
|
||||
td = tup->t_data;
|
||||
tup_desc = slot->tts_tupleDescriptor;
|
||||
HeapTupleHeaderSetDatumLength(td, tup->t_len);
|
||||
|
|
@ -660,6 +814,17 @@ Datum ExecFetchSlotTupleDatum(TupleTableSlot* slot)
|
|||
HeapTupleHeaderSetTypMod(td, tup_desc->tdtypmod);
|
||||
return PointerGetDatum(td);
|
||||
}
|
||||
/*
|
||||
函数ExecFetchSlotTupleDatum用于从元组表槽中检索表示元组的Datum。让我们一步一步地浏览代码:
|
||||
该函数声明类型为HeapTuple的变量tup、类型为HeapStupleHeader的变量td和类型为TupleDesc的变量tup_desc。这些变量将分别用于存储元组、元组头和元组描述符。
|
||||
该函数调用ExecMaterializeSlot来实现槽,这意味着它在内存中创建元组的物理表示。这是必要的,因为返回的Datum将是指向元组标头的指针,并且需要具体化槽才能访问标头。
|
||||
函数将物化元组分配给元组变量。
|
||||
函数将元组头(tup->t_data)分配给td变量。
|
||||
函数将槽的元组描述符(slot->tts_tupleDescriptor)分配给tup_desc变量。
|
||||
该函数使用HeapTupleHeaderSetDatumLength、HeapTuppleHeaderSetTypeId和HeapTupleHeaderSetTypMod宏,
|
||||
根据元组描述符(tup_desc)的长度、类型ID和类型修饰符,在元组标头(td)中设置适当的字段。
|
||||
最后,函数通过调用PointerGetDatum(td)返回元组头(td)的Datum表示。
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecMaterializeSlot
|
||||
|
|
@ -684,6 +849,14 @@ HeapTuple ExecMaterializeSlot(TupleTableSlot* slot)
|
|||
|
||||
return tableam_tslot_materialize(slot);
|
||||
}
|
||||
/*
|
||||
函数ExecMaterializeSlot用于实体化存储在元组表槽中的元组。让我们分解代码:
|
||||
该函数以TupleTableSlot指针作为输入,该指针表示包含要具体化的元组的槽。
|
||||
该函数执行一些健全性检查,以确保输入槽有效且不为空。它检查slot是否不为NULL,以及slot->tts_isempty是否为false。
|
||||
如果健全性检查通过,该函数将调用tableam_tslot_materialize来实现槽中的元组。此函数负责在内存中创建元组的物理表示。
|
||||
最后,函数将物化元组作为HeapTuple对象返回。
|
||||
注意:该代码假定存在一个函数tableam_tslot_materialize,该函数处理元组的物化。此功能的实现方式可能因所使用的特定表访问方法而异
|
||||
*/
|
||||
|
||||
/* --------------------------------
|
||||
* ExecCopySlot
|
||||
|
|
@ -701,9 +874,8 @@ TupleTableSlot* ExecCopySlot(TupleTableSlot* dst_slot, TupleTableSlot* src_slot)
|
|||
MemoryContext old_context;
|
||||
|
||||
/*
|
||||
* There might be ways to optimize this when the source is virtual, but
|
||||
* for now just always build a physical copy. Make sure it is in the
|
||||
* right context.
|
||||
* 当源是虚拟的时,可能有一些方法可以优化这一点,
|
||||
* 但目前只是始终构建一个物理副本。确保它在正确的上下文中。
|
||||
*/
|
||||
old_context = MemoryContextSwitchTo(dst_slot->tts_mcxt);
|
||||
new_tuple = ExecCopySlotTuple(src_slot);
|
||||
|
|
@ -711,6 +883,17 @@ TupleTableSlot* ExecCopySlot(TupleTableSlot* dst_slot, TupleTableSlot* src_slot)
|
|||
|
||||
return ExecStoreTuple(new_tuple, dst_slot, InvalidBuffer, true);
|
||||
}
|
||||
/*
|
||||
函数ExecCopySlot用于创建存储在源元组表槽中的元组的副本,并将其存储在目标元组表槽。
|
||||
该函数采用两个TupleTableSlot指针作为输入:dst_slot,它表示将存储复制的元组的目标槽,src_slot,它表示包含要复制元组的源槽。
|
||||
函数声明类型为HeapTuple的变量new_tuple和类型为MemoryContext的变量old_context。这些变量将分别用于存储复制的元组和先前的内存上下文。
|
||||
该函数使用MemoryContext SwitchTo将内存上下文切换到目标插槽的内存上下文。这样可以确保在正确的内存上下文中分配复制的元组。
|
||||
函数调用ExecCopySlotTuple来创建存储在源槽中的元组的副本。此函数负责为新元组分配内存,并从原始元组复制数据。
|
||||
函数使用MemoryContext SwitchTo(old_context)将内存上下文切换回上一个上下文。这样可以确保内存上下文恢复到其原始状态。
|
||||
最后,函数调用ExecStoreTuple将复制的元组存储在目标槽中。它传递复制的元组(new_tuple)、目标槽(dst_slot)、无效缓冲区(InvalidBuffer)和指示元组是虚拟元组的标志(true)。
|
||||
函数返回目标插槽指针。
|
||||
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* convenience initialization routines
|
||||
|
|
@ -741,6 +924,17 @@ void ExecInitScanTupleSlot(EState* estate, ScanState* scan_state, TableAmType ta
|
|||
{
|
||||
scan_state->ss_ScanTupleSlot = ExecAllocTableSlot(&estate->es_tupleTable, tam);
|
||||
}
|
||||
/*
|
||||
函数ExecInitResultTupleSlot和ExecInitScanTupleSlot分别用于初始化用于存储结果元组和扫描元组的元组槽。
|
||||
ExecInitResultTupleSlot函数:
|
||||
它有三个参数:表示执行状态的estate、表示计划节点状态的plan_state和表示表访问方法类型的tam。
|
||||
它使用ExecAllocTableSlot函数将plan_state的结果元组槽分配给新分配的表槽。表槽是使用estate->es_tupleTable从estate的元组表中获得的。
|
||||
新分配的表槽被分配给plan_state->ps_ResultTupleSlot,它表示计划节点的结果元组槽。
|
||||
ExecInitScanTupleSlot函数:
|
||||
它有三个参数:表示执行状态的estate、表示扫描节点状态的scan_state和表示表访问方法类型的tam。
|
||||
它使用ExecAllocTableSlot函数将scan_state的扫描元组槽分配给新分配的表槽。表槽是使用estate->es_tupleTable从estate的元组表中获得的。
|
||||
新分配的表槽被分配给scan_state->ss_ScanTupleSlot,它表示扫描节点的扫描元组槽。
|
||||
*/
|
||||
|
||||
/* ----------------
|
||||
* ExecInitExtraTupleSlot
|
||||
|
|
@ -767,6 +961,13 @@ TupleTableSlot* ExecInitNullTupleSlot(EState* estate, TupleDesc tup_type)
|
|||
|
||||
return ExecStoreAllNullTuple(slot);
|
||||
}
|
||||
/*
|
||||
函数ExecInitNullTupleSlot用于使用null元组初始化元组表槽。
|
||||
该函数采用两个参数:表示执行状态的estate和表示null元组的元组描述符的tup_type。
|
||||
函数调用ExecInitTextraTupleSlot来初始化一个额外的元组表槽。此函数负责从执行状态的元组表中分配和初始化一个新的元组表槽。
|
||||
函数使用ExecSetSlotDescriptor将元组描述符tup_type分配给插槽。此函数将槽的描述符设置为提供的元组描述符。
|
||||
最后,该函数返回使用初始化的slot调用ExecStoreAllNullTuple的结果。此函数负责通过将所有属性值设置为null来用null元组填充槽。
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecTypeFromTL
|
||||
|
|
@ -829,6 +1030,20 @@ static TupleDesc ExecTypeFromTLInternal(List* target_list, bool has_oid, bool sk
|
|||
|
||||
return type_info;
|
||||
}
|
||||
/*
|
||||
函数ExecCleanTypeFromTL用于从目标列表生成干净的元组描述符。
|
||||
该函数采用三个参数:target_list,表示目标列表;has_oid,表示生成的元组描述符是否应包括oid列的布尔值;tam,表示表访问方法类型。
|
||||
该函数使用适当的参数调用ExecTypeFromTLInternal函数以生成元组描述符。它传递target_list、has_oid、true(跳过垃圾条目)、false(不标记丢弃的列)和tam(表访问方法类型)。
|
||||
ExecTypeFromTLInternal函数初始化一些变量,包括type_info,一个TupleDesc对象,它将保存生成的元组描述符,以及len,目标列表的长度。
|
||||
该函数使用CreateTemplateTupleDesc创建模板元组描述符,传递len、has_oid和tam。这将创建一个具有指定数量的属性和OID标志的空元组描述符。
|
||||
该函数使用foreach循环迭代目标列表中的每个目标条目。
|
||||
如果skip_junk为true,并且当前目标条目是一个垃圾条目(resjunk为true),则循环将继续到下一次迭代。
|
||||
函数使用TupleDescInitEntry初始化元组描述符中的一个条目。它设置属性编号(cur_resno)、属性名称(resname)、
|
||||
属性类型(exprType((Node*)ttle->expr))、属性typmod(exprTypmod((Node*ttle->expr)),以及属性排序规则的默认值0。
|
||||
如果mark_dropped为true,并且属性名称包含字符串“……..pg.dropped.”,则通过将attitdropped设置为true,将该属性标记为已删除。
|
||||
cur_resno将递增,为下一个属性做准备。
|
||||
在对所有目标条目进行迭代之后,函数将返回生成的元组描述符。
|
||||
*/
|
||||
|
||||
/*
|
||||
* ExecTypeFromExprList - build a tuple descriptor from a list of Exprs
|
||||
|
|
@ -858,6 +1073,18 @@ TupleDesc ExecTypeFromExprList(List* expr_list, List* names_list, TableAmType t
|
|||
|
||||
return type_info;
|
||||
}
|
||||
/*
|
||||
函数ExecTypeFromExprList用于从表达式列表和相应的名称列表生成元组描述符。让我们分解代码:
|
||||
该函数采用三个参数:expr_list,表示表达式列表,names_list,表示与表达式对应的名称列表,tam,表示表访问方法类型。
|
||||
该函数初始化变量,包括type_info(一个TupleDesc对象,它将保存生成的元组描述符)和cur_resno(一个表示当前属性编号的整数)。
|
||||
该函数使用Assert断言expr_list和names_list的长度相等。这样可以确保每个表达式都有相应的名称。
|
||||
该函数使用CreateTemplateTupleDesc创建模板元组描述符,传递expr_list的长度,false表示元组描述符不应包括OID列,tam表示表访问方法类型。
|
||||
该函数使用forboth循环并行迭代每个表达式和名称。
|
||||
函数使用TupleDescInitEntry初始化元组描述符中的一个条目。它为属性的排序规则设置属性编号(cur_resno)、属性名称(n)、属性类型(exprType(e))、属性typmod(exprTypmod(e))和默认值0。
|
||||
函数使用TupleDescInitEntryCollation和exprCollation(e)设置属性的排序规则。
|
||||
cur_resno将递增,为下一个属性做准备。
|
||||
在对所有表达式和名称进行迭代之后,函数将返回生成的元组描述符。
|
||||
*/
|
||||
|
||||
/*
|
||||
* BlessTupleDesc - make a completed tuple descriptor useful for SRFs
|
||||
|
|
@ -895,6 +1122,16 @@ TupleTableSlot* TupleDescGetSlot(TupleDesc tup_desc)
|
|||
/* Return the slot */
|
||||
return slot;
|
||||
}
|
||||
/*
|
||||
函数BlessTupleDesc用于祝福元组描述符。函数TupleDescGetSlot用于根据提供的元组描述符初始化元组表槽。让我们分解代码:
|
||||
函数BlessTupleDesc将TupleDesc对象tup_desc作为输入。
|
||||
如果tup_desc的tdtypeid是RECORDOID,并且tdtypmod小于0,则表示元组描述符表示通用记录类型。在这种情况下,函数调用assign_record_type_typmod为记录类型分配一个合适的typmod。
|
||||
函数返回tup_desc对象。这样做是为了方便表示,因为函数不直接修改元组描述符。
|
||||
函数TupleDescGetSlot将TupleDesc对象tup_desc作为输入。
|
||||
该函数调用BlessTupleDesc来祝福元组描述符。这样做是为了确保元组描述符正确初始化并准备好使用。
|
||||
该函数调用MakeSingleTupleTableSlot,根据提供的元组描述符创建一个独立的元组表槽。
|
||||
函数返回创建的元组表槽。
|
||||
*/
|
||||
|
||||
/*
|
||||
* TupleDescGetAttInMetadata - Build an AttInMetadata structure based on the
|
||||
|
|
@ -914,18 +1151,18 @@ AttInMetadata* TupleDescGetAttInMetadata(TupleDesc tup_desc)
|
|||
|
||||
att_in_meta = (AttInMetadata*)palloc(sizeof(AttInMetadata));
|
||||
|
||||
/* "Bless" the tupledesc so that we can make rowtype datums with it */
|
||||
/* "Bless" the tupledesc so that we can make rowtype datums with it(“Bless”元组,这样我们就可以用它制作行型基准) */
|
||||
att_in_meta->tupdesc = BlessTupleDesc(tup_desc);
|
||||
|
||||
/*
|
||||
* Gather info needed later to call the "in" function for each attribute
|
||||
* Gather info needed later to call the "in" function for each attribute(收集稍后调用每个属性的“in”函数所需的信息)
|
||||
*/
|
||||
att_in_func_info = (FmgrInfo*)palloc0(natts * sizeof(FmgrInfo));
|
||||
att_io_params = (Oid*)palloc0(natts * sizeof(Oid));
|
||||
att_typ_mods = (int32*)palloc0(natts * sizeof(int32));
|
||||
|
||||
for (i = 0; i < natts; i++) {
|
||||
/* Ignore dropped attributes */
|
||||
/* Ignore dropped attributes(忽略丢弃的属性) */
|
||||
if (!tup_desc->attrs[i]->attisdropped) {
|
||||
att_type_id = tup_desc->attrs[i]->atttypid;
|
||||
getTypeInputInfo(att_type_id, &att_in_func_id, &att_io_params[i]);
|
||||
|
|
@ -939,6 +1176,20 @@ AttInMetadata* TupleDescGetAttInMetadata(TupleDesc tup_desc)
|
|||
|
||||
return att_in_meta;
|
||||
}
|
||||
/*
|
||||
函数TupleDescGetAttInMetadata用于收集调用元组描述符中每个属性的“in”函数所需的信息。让我们分解代码:
|
||||
该函数采用TupleDesc对象tup_desc作为输入。
|
||||
它初始化变量,包括natts、元组描述符中的属性数量、i、循环计数器以及用于存储属性信息的各种其他变量。
|
||||
它使用palloc为AttInMetadata对象att_in_meta分配内存。此对象将保存收集到的属性信息。
|
||||
它通过调用BlessTupleDesc来“祝福”元组描述符。这样可以确保元组描述符正确初始化并准备好使用。
|
||||
它初始化数组att_in_func_info、att_io_params和att_typ_mod,以存储每个属性所需的信息。
|
||||
它使用循环对元组描述符中的每个属性进行迭代。
|
||||
如果属性未被丢弃(attidrepped为false),它将使用getTypeInputInfo收集诸如属性类型ID、“in”函数ID和IO参数等信息。
|
||||
它使用fmgr_info用“in”函数信息初始化att_in_func_info数组。
|
||||
它将IO参数和属性类型mod存储在相应的数组中。
|
||||
它将数组分配给att_in_meta对象中的相应字段。
|
||||
它返回att_in_meta对象。
|
||||
*/
|
||||
|
||||
/*
|
||||
* BuildTupleFromCStrings - build a HeapTuple given user data in C string form.
|
||||
|
|
@ -986,6 +1237,19 @@ HeapTuple BuildTupleFromCStrings(AttInMetadata* att_in_meta, char** values)
|
|||
|
||||
return tuple;
|
||||
}
|
||||
/*
|
||||
函数BuildTupleFromCStrings用于从C样式字符串数组中构建HeapTuple。
|
||||
该函数接受一个AttInMetadata对象att_in_meta和一个C样式字符串值数组作为输入。
|
||||
它从att_in_meta对象中提取TupleDesc对象tup_desc,并获取元组描述符中属性natt的数量。
|
||||
它初始化数组d_values和nulls,分别存储属性值和null标志。
|
||||
它使用palloc为d_values和null分配内存。
|
||||
它使用循环对元组描述符中的每个属性进行迭代。
|
||||
如果该属性未被丢弃(attitdropped为false),它将使用InputFunctionCall为该属性调用“in”函数。此函数使用适当的输入函数将C样式字符串值转换为基准。它还根据值是否为null来设置null标志。
|
||||
如果属性被删除,它会将属性值设置为NULL,并将NULL标志设置为true。
|
||||
在迭代所有属性之后,它使用tableam_tops_form_tuple形成一个HeapTuple。此函数基于提供的元组描述符、属性值和null标志创建一个新的HeapTuple。
|
||||
它使用pfree_ext释放为d_values和null分配的内存。
|
||||
它返回创建的HeapTuple。
|
||||
*/
|
||||
|
||||
/*
|
||||
* Functions for sending tuples to the frontend (or other specified destination)
|
||||
|
|
@ -1006,6 +1270,17 @@ TupOutputState* begin_tup_output_tupdesc(DestReceiver* dest, TupleDesc tup_desc)
|
|||
|
||||
return tstate;
|
||||
}
|
||||
/*
|
||||
函数begin_tup_output_tupdesc用于初始化给定目标接收器和元组描述符的元组输出状态。
|
||||
该函数采用两个参数:dest,表示目标接收器,tup_desc,表示元组描述符。
|
||||
它声明了一个TupOutputState类型的变量tstate。
|
||||
它使用palloc为tstate对象分配内存。
|
||||
它通过使用提供的tup_desc调用MakeSingleTupleTableSlot来初始化tstate的slot字段。这将基于元组描述符创建一个单元组表槽。
|
||||
它将dest参数指定给tstate的dest字段。
|
||||
它使用(*tstate->dest->rStartup)调用目标接收器的rStartup函数。此功能负责初始化目标接收器并执行任何必要的设置。
|
||||
rStartup函数是使用目标接收器、命令类型(在本例中为CMD_SELECT)和元组描述符调用的。
|
||||
最后,函数返回tstate对象。
|
||||
*/
|
||||
|
||||
/*
|
||||
* write a single tuple
|
||||
|
|
@ -1038,11 +1313,24 @@ void do_tup_output(TupOutputState* tstate, Datum* values, size_t values_len, con
|
|||
/* clean up */
|
||||
(void)ExecClearTuple(slot);
|
||||
}
|
||||
/*
|
||||
函数do_tup_output用于使用提供的TupOutputState对象将元组输出到目标接收器。
|
||||
该函数采用五个参数:tstate,它表示元组输出状态,values,它是元组的Datum值的数组,values_len,values数组的长度,is_null,它是指示每个属性是否为null的布尔值的数组;is_null_len,is _null数组的长度。
|
||||
它使用Assert断言值和is_null数组不为null,并且它们的长度不为零。这样可以确保提供有效的数据。
|
||||
它声明了一个TupleTableSlot类型的变量slot,并为其分配tstate对象的slot字段。
|
||||
它从插槽的tts_tupleDescriptor字段中获取属性natt的数量。
|
||||
它使用ExecClearTuple清除插槽,以确保插槽为空。
|
||||
它使用memcpy_s将值数组复制到插槽的tts_values字段中。
|
||||
它使用memcpy_s将is_null数组复制到插槽的tts_isull字段中。
|
||||
它使用ExecStoreVirtualTuple将插槽标记为包含虚拟元组。
|
||||
它通过调用目标接收器的receiveSlot函数,将slot和tstate->dest作为参数,将元组发送到目标接收器。
|
||||
它使用ExecClearTuple再次清除插槽以清除任何剩余数据。
|
||||
*/
|
||||
|
||||
/*
|
||||
* write a chunk of text, breaking at newline characters
|
||||
* write a chunk of text, breaking at newline characters(写一大块文本,换行)
|
||||
*
|
||||
* Should only be used with a single-TEXT-attribute tupdesc.
|
||||
* Should only be used with a single-TEXT-attribute tupdesc.(应仅与单个TEXT属性tupdesc一起使用)
|
||||
*/
|
||||
int do_text_output_multiline(TupOutputState* tstate, char* text)
|
||||
{
|
||||
|
|
@ -1072,6 +1360,20 @@ int do_text_output_multiline(TupOutputState* tstate, char* text)
|
|||
}
|
||||
return tuple_count;
|
||||
}
|
||||
/*函数do_text_output_multiline用于使用提供的TupOutputState对象将多行文本作为元组输出到目标接收器。
|
||||
该函数接受两个参数:tstate,它表示元组输出状态,text,它是指向输入文本的指针。
|
||||
声明了一个长度为1的Datum类型的数组值。此数组将保存每个元组的文本值。
|
||||
声明了一个bool类型的数组is_null,长度为1,初始化为false。此数组指示每个元组的属性是否为null。
|
||||
声明了一个变量tuple_count,并将其初始化为0。这个变量将跟踪输出的元组的数量。
|
||||
只要文本字符串中有字符,函数就会进入一个循环。
|
||||
使用strchr搜索换行符('\n')的下一个出现。如果找到,它会计算行的长度,并增加eol指针以指向换行符之后的下一个字符。如果找不到,它会将长度设置为文本字符串中的剩余字符,并将eol设置为指向字符串的末尾。
|
||||
使用cstring_to_text_with_len将文本行转换为基准,并将其指定给值[0]。
|
||||
调用do_tup_output函数来输出具有值数组、is_null数组和提供的tstate的元组。
|
||||
递增tuple_count变量。
|
||||
使用pfree释放为Datum值分配的内存。
|
||||
更新文本指针以指向下一行文本(在换行符之后)。
|
||||
循环结束后,返回元组计数。
|
||||
*/
|
||||
|
||||
void end_tup_output(TupOutputState* tstate)
|
||||
{
|
||||
|
|
@ -1149,3 +1451,28 @@ TupleTableSlot* ExecStoreDataRowTuple(char* msg, size_t len, Oid msgnode_oid, Tu
|
|||
return slot;
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
两个独立的函数:end_tup_output和ExecStoreDataRowTuple。
|
||||
|
||||
end_tup_output:
|
||||
此函数用于清理和最终确定元组输出状态。
|
||||
它接受一个TupOutputState对象tstate作为输入。
|
||||
它使用(*tstate->dest->rShutdown)调用目标接收器的rShutdownfunction。此功能负责关闭目标接收器并执行任何必要的清理。
|
||||
它使用ExecDropSingleTupleTableSlot删除单元组表槽。
|
||||
它使用pfree_ext释放为tstate对象分配的内存。
|
||||
*/
|
||||
|
||||
/*
|
||||
ExecStoreDataRowTuple:
|
||||
此函数用于将DataRow消息格式的缓冲区存储到元组表槽中。
|
||||
它需要几个参数:msg,它表示包含DataRow消息的缓冲区,len,它是缓冲区的长度,msgnode_oid,它表示与消息相关的节点oid,slot,它是将缓冲区存储到的元组表slot,should_free,一个指示缓冲区是否应该释放的布尔标志。
|
||||
它执行若干健全性检查以确保输入参数的有效性。
|
||||
它释放属于插槽的任何旧物理元组。
|
||||
它释放属于插槽的任何旧的最小元组。
|
||||
如果需要,它会释放插槽中的dataRow。
|
||||
它重置插槽的每个元组上下文。
|
||||
它释放引用缓冲区上的引脚(如果有)。
|
||||
它通过更新槽的相关字段将新元组存储到指定的槽中。
|
||||
它将提取的状态标记为无效。
|
||||
最后,它返回更新后的插槽。
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue