【OpenGauss源码学习 —— (ALTER TABLE(ATRewriteTable))】

发布于:2024-04-29 ⋅ 阅读:(25) ⋅ 点赞:(0)

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss5.1.0 的开源代码和《OpenGauss数据库源码解析》一书

概述

  【OpenGauss源码学习 —— (ALTER TABLE(Add Column))】这篇文章深入分析了 OpenGauss 数据库管理系统中执行 ALTER TABLE (Add Column) 命令的源码实现。文章探讨了系统如何处理向表中添加新列的过程,包括检查约束、设置默认值以及保证数据完整性的机制。特别提到了 ATExecAddColumn 函数,它负责管理添加列的过程。此外,文章还为理解更复杂的表结构修改提供了基础,特别是涉及到 ATRewriteTables 函数,该函数对于处理表结构变化和数据重组至关重要。
  在 ATRewriteTables 函数中,ExecRewriteFuncPtrArray[rel_format_idx][idxPartitionedOrNot](tab, NewTableSpace, lockmode); 用于执行表的重写操作rel_format_idxidxPartitionedOrNot两个索引分别决定了使用哪种表的格式(行存或列存)和表的类型(是否为分区表)。这个函数通常在需要重构表的物理存储或迁移表到新的表空间时被调用,例如在改变列类型或调整表空间配置时。通过传递 tab表的修改信息)、NewTableSpace新的表空间)和 lockmode锁模式),它负责实际的数据复制和结构调整,确保操作的完整性和数据的一致性。
  本文主要学习 ATRewriteTable 函数的作用。

ExecRewriteRowTable 函数

  ExecRewriteRowTable 函数用于在表结构变更(如数据类型更改压缩方式更改)需要重写表数据时执行。它首先创建一个新的堆表,然后将旧表数据按照新的表结构要求复制到新表中,同时验证数据是否符合新加的约束。在数据复制完成后,它会交换旧表和新表的物理文件位置,并更新系统的目录信息,最终丢弃旧表。这个过程确保了表数据的完整性和一致性,同时最小化了对数据库操作的影响。函数源码如下所示:(src\gausskernel\optimizer\commands\tablecmds.cpp

/*
*@说明:重写行关系数据。
*@Param[IN]lockmode:重写数据时使用的锁定模式
*@Param[IN]NewTableSpace:行关系使用的新表空间
*@Param[IN]选项卡:更改表格信息
*@另请参阅:
*/
static void ExecRewriteRowTable(AlteredTableInfo* tab, Oid NewTableSpace, LOCKMODE lockmode)
{
	// 禁止重写或测试列存索引
	ForbidToRewriteOrTestCstoreIndex(tab);
	// 创建一个新的堆表,返回新表的 OID
	Oid OIDNewHeap = make_new_heap(tab->relid, NewTableSpace);
	
	/*
	 * 将旧表的数据复制到新表,并对旧表中的数据执行新的约束检测
	 */
	Relation oldRel = heap_open(tab->relid, NoLock); // 打开旧表
	Relation newRel = heap_open(OIDNewHeap, lockmode); // 打开新表
	
	/*
	 * 临时设置旧关系的 relOptions 为修改前的选项,以执行表重写
	 */
	if (tab->rewrite == AT_REWRITE_ALTER_COMPRESSION) {
	    oldRel->rd_node.opt = tab->opt;
	}
	// 执行表重写操作
	ATRewriteTable(tab, oldRel, newRel);
	heap_close(oldRel, NoLock); // 关闭旧表
	heap_close(newRel, NoLock); // 关闭新表
	
	/*
	 * 交换旧表和新表的物理文件,然后重建索引并丢弃旧表。
	 * 因为在 ATRewriteTable 中重写了所有元组,所以可以使用 RecentXmin 作为新表的 relfrozenxid。
	 * 不尝试通过内容交换 toast 表,因为这段代码不适用于系统目录。
	 */
	finish_heap_swap(tab->relid, OIDNewHeap, false, false, true, u_sess->utils_cxt.RecentXmin,
	                 GetOldestMultiXactId(), NULL, tab);
	
	// 清除所有的 attrinitdefval
	clearAttrInitDefVal(tab->relid);
	}

ATRewriteTable 函数

  ATRewriteTable 函数主要用于处理表结构变更时的数据迁移,特别是当表为分桶存储时。它根据是否为分桶表采取不同的处理策略:对于分桶表,它会遍历每个桶,对每个桶使用 ATRewriteTableInternal 进行数据重写,然后关闭相关的桶关系;对于非分桶表,直接对整个表进行数据重写。此外,还会在适当的条件下对新表进行数据同步,以确保数据的一致性和完整性。这个过程对于保证数据库结构变更的顺利进行至关重要。具体逻辑如下:

  1. 检查旧表 oldrel 是否创建了桶(分桶存储),如果是,则获取该表的桶信息。
  2. 遍历每个桶,使用 bucketGetRelation 获取旧桶和新桶的关系句柄。
  3. 对每对旧桶和新桶调用 ATRewriteTableInternal 函数进行数据重写。
  4. 重写完成后,通过 bucketCloseRelation 关闭桶的关系句柄。
  5. 如果存在新表 newrel,并且当前的操作环境允许,会对新表进行数据同步操作,确保数据的一致性。
  6. 如果旧表不是分桶存储的表,则直接对旧表和新表调用 ATRewriteTableInternal 进行重写。

  函数源码如下所示(路径:src\gausskernel\optimizer\commands\tablecmds.cpp

// 定义重写表数据的函数
static void ATRewriteTable(AlteredTableInfo* tab, Relation oldrel, Relation newrel)
{
    // 如果旧表是分桶表,则进行分桶处理
    if (RELATION_CREATE_BUCKET(oldrel)) {
        // 获取旧表的桶列表
        oidvector* bucketlist = searchHashBucketByOid(oldrel->rd_bucketoid);

        // 遍历所有桶
        for (int i = 0; i < bucketlist->dim1; i++) {
            // 获取旧桶的关系
            Relation oldbucket = bucketGetRelation(oldrel, NULL, bucketlist->values[i]);
            Relation newbucket = NULL;
            // 如果有新表,则获取新桶的关系
            if (newrel != NULL) {
                newbucket = bucketGetRelation(newrel, NULL, bucketlist->values[i]);
            }

            // 对每个桶执行重写操作
            ATRewriteTableInternal(tab, oldbucket, newbucket);

            // 关闭旧桶的关系
            bucketCloseRelation(oldbucket);
            // 如果有新桶,关闭新桶的关系
            if (newbucket != NULL) {
                bucketCloseRelation(newbucket);
            }
        }

        // 如果存在新表且满足一定条件,则同步新表数据
        if (newrel && (!XLogIsNeeded() || enable_heap_bcm_data_replication()) && !RelationIsSegmentTable(newrel)) {
            heap_sync(newrel);
        }
    } else {
        // 如果不是分桶表,直接对旧表和新表执行内部重写函数
        ATRewriteTableInternal(tab, oldrel, newrel);
    }
}

ATRewriteTableInternal 函数

  ATRewriteTableInternal 函数是在数据库系统中用于修改表结构的函数。功能包括检查并重新构建数据表,同时处理约束和默认值等元数据。具体来说,函数首先定义了一系列变量用于记录旧表和新表的描述信息,检查是否需要扫描旧表,以及处理 NOT NULL 约束等。接着,代码准备执行状态插入选项,以优化数据插入过程,例如在适当时跳过 WAL 日志以加快处理速度。此外,还有对表达式的初始化和检查,特别是处理新加的或修改的约束表达式。如果表结构发生变化或者添加了新的非空约束,需要重新检查并应用这些约束。整体而言,此函数主要用于在数据库中执行表结构的变更操作,确保数据的完整性和一致性,同时优化表的存储和访问效率。函数的执行流程如下:

  1. 初始化: 设置旧表和新表的描述符,确定是否需要扫描(基于约束的改变)。
  2. 处理批量插入: 如果新表存在,配置批量插入状态和相关选项,以优化性能。
  3. 生成执行状态: 为约束和默认值的检查创建执行状态。
  4. 构建表达式执行状态: 为所有约束生成执行状态,特别是检查约束。
  5. 处理 NOT NULL 约束: 如果添加了新的 NOT NULL 约束或进行了重建,检查所有非空约束。
  6. 数据重写或扫描: 如果新表存在或需要扫描,进行数据处理。处理包括:
    • 扫描旧表。
    • 针对每个元组,根据新的结构进行重建或验证。
    • 应用新的约束和检查数据完整性。
/*
 * change ATRewriteTable() input: oid->rel
 */
/*
 * ATRewriteTable: scan or rewrite one table
 *
 * oldrel is NULL if we don't need to rewrite
 */
static void ATRewriteTableInternal(AlteredTableInfo* tab, Relation oldrel, Relation newrel)
{
    // 定义旧表和新表的描述对象
    TupleDesc oldTupDesc;
    TupleDesc newTupDesc;
    bool needscan = false; // 是否需要扫描旧表
    List* notnull_attrs = NIL; // 需要检查NOT NULL约束的属性列表
    int i;
    ListCell* l = NULL;
    EState* estate = NULL; // 执行状态
    CommandId mycid;
    BulkInsertState bistate;
    uint32 hi_options; // 插入选项

    // 获取旧表和新表的元组描述
    oldTupDesc = tab->oldDesc;
    newTupDesc = RelationGetDescr(oldrel); /* 包括所有修改 */

    /*
     * 准备批量插入状态和选项,因为我们正在构建新堆,可以在结束时跳过WAL日志并同步到磁盘
     */
    if (newrel) {
        mycid = GetCurrentCommandId(true);
        bistate = GetBulkInsertState();

        hi_options = TABLE_INSERT_SKIP_FSM; // 跳过空闲空间映射
        if (!XLogIsNeeded())
            hi_options |= TABLE_INSERT_SKIP_WAL; // 跳过WAL日志
    } else {
        /* 保持编译器安静,不使用未初始化的变量 */
        mycid = 0;
        bistate = NULL;
        hi_options = 0;
    }

    /*
     * 生成约束和默认值的执行状态
     */
    estate = CreateExecutorState();

    /* 构建必要的表达式执行状态 */
    foreach (l, tab->constraints) {
        NewConstraint* con = (NewConstraint*)lfirst(l);

        switch (con->contype) {
            case CONSTR_CHECK: // 检查约束
                needscan = true;
                if (estate->es_is_flt_frame){
                    con->qualstate = (List*)ExecPrepareExprList((List*)con->qual, estate);
                } else {
                    con->qualstate = (List*)ExecPrepareExpr((Expr*)con->qual, estate);
                }
                break;
            case CONSTR_FOREIGN:
                /* 外键约束,不执行操作 */
                break;
            default: {
                ereport(ERROR,
                    (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                        errmsg("unrecognized constraint type: %d", (int)con->contype)));
            } break;
        }
    }

    foreach (l, tab->newvals) {
        NewColumnValue* ex = (NewColumnValue*)lfirst(l);

        /* 表达式已经计划好 */
        ex->exprstate = ExecInitExpr((Expr*)ex->expr, NULL);
    }

    notnull_attrs = NIL;
    if (newrel || tab->new_notnull) {
        /*
         * 如果我们正在重建元组或添加了新的NOT NULL约束,则检查所有非空约束。
         */
        for (i = 0; i < newTupDesc->natts; i++) {
            if (newTupDesc->attrs[i].attnotnull && !newTupDesc->attrs[i].attisdropped)
                notnull_attrs = lappend_int(notnull_attrs, i);
        }
        if (notnull_attrs != NULL)
            needscan = true;
    }

    if (newrel || needscan) {
        // 如果需要新表或需要扫描,则进行相应处理
        ExprContext* econtext = NULL;
        Datum* values = NULL;
        bool* isnull = NULL;
        bool isUstore = false;
        TupleTableSlot* oldslot = NULL;
        TupleTableSlot* newslot = NULL;
        TableScanDesc scan;
        HeapTuple tuple;
        UHeapTuple utuple;
        MemoryContext oldCxt;
        List* dropped_attrs = NIL;
        ListCell* lc = NULL;
        errno_t rc = EOK;
        int128 autoinc = 0;
        bool need_autoinc = false;
        bool has_generated = false;
        AttrNumber autoinc_attnum = (newTupDesc->constr && newTupDesc->constr->cons_autoinc) ?
            newTupDesc->constr->cons_autoinc->attnum : 0;

        isUstore = RelationIsUstoreFormat(oldrel);

        if (newrel)
            ereport(DEBUG1, (errmsg("rewriting table \"%s\"", RelationGetRelationName(oldrel))));
        else
            ereport(DEBUG1, (errmsg("verifying table \"%s\"", RelationGetRelationName(oldrel))));

        if (newrel) {
			/*
			 * 元组或页面上的所有谓词锁都将被创建无效,
			 * 因为我们四处移动元组。将它们升级为关系锁。
			 */
            TransferPredicateLocksToHeapRelation(oldrel);
        }

        econtext = GetPerTupleExprContext(estate);

        /*
         * 为旧表和新表创建元组槽。即使元组相同,元组描述符可能不同(例如添加列但不设置默认值的情况)。
         */
        oldslot = MakeSingleTupleTableSlot(oldTupDesc, false, oldrel->rd_tam_ops);  // 为旧表创建一个元组槽
        newslot = MakeSingleTupleTableSlot(newTupDesc, false, oldrel->rd_tam_ops);  // 为新表创建一个元组槽

        /* 预先分配值和空值标识数组 */
        i = Max(newTupDesc->natts, oldTupDesc->natts);  // 获取最大的属性数量
        values = (Datum*)palloc(i * sizeof(Datum));  // 分配内存以存储值
        isnull = (bool*)palloc(i * sizeof(bool));  // 分配内存以存储空值标识
        rc = memset_s(values, i * sizeof(Datum), 0, i * sizeof(Datum));  // 初始化值数组
        securec_check(rc, "\0", "\0");  // 检查初始化值数组是否成功
        rc = memset_s(isnull, i * sizeof(bool), true, i * sizeof(bool));  // 初始化空值标识数组
        securec_check(rc, "\0", "\0");  // 检查初始化空值标识数组是否成功

        /*
         * 根据新的元组描述符设置被删除的属性为NULL。我们预先计算被删除的属性列表,避免在每个元组循环中重复此操作。
         */
        for (i = 0; i < newTupDesc->natts; i++) {  // 遍历新表的所有属性
            if (newTupDesc->attrs[i].attisdropped)  // 如果属性被标记为删除
                dropped_attrs = lappend_int(dropped_attrs, i);  // 将该属性索引添加到被删除属性列表
        }

        /*
         * 这里我们不关心oldTupDesc的initdefvals,因为它在解构旧元组时处理。新添加的列的值可能来自*tab->newvals*列表,或newTupDesc的initdefvals列表。
         */
        if (newTupDesc->initdefvals) {  // 如果新描述符有默认值
            TupInitDefVal* defvals = newTupDesc->initdefvals;  // 获取默认值

            /* 跳过已存在的列 */
            for (i = oldTupDesc->natts; i < newTupDesc->natts; ++i) {  // 遍历新添加的列
                if (!defvals[i].isNull) {  // 如果新列有默认值
                    isnull[i] = false;  // 设置该列的空值标识为false
                    values[i] = fetchatt(&newTupDesc->attrs[i], defvals[i].datum);  // 获取该列的默认值
                }
            }
        }

        /*
         * 扫描行数据,如果需要,生成新行,然后检查所有约束。
         */
        scan = tableam_scan_begin(oldrel, SnapshotNow, 0, NULL);  // 开始扫描旧表

        /*
         * 切换到每个元组的内存上下文,并在生成每个元组后重置,以防内存泄漏。
         */
        oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));  // 切换到元组级内存上下文

        // 特殊情况,必须使用oldTupDesc来解构堆元组,因此这里覆盖scan->rs_tupdesc。
        if (isUstore) {
            ((UHeapScanDesc) scan)->rs_tupdesc = oldTupDesc;
            while ((UHeapGetNextSlotGuts(scan, ForwardScanDirection, oldslot)) != NULL)  // 循环遍历扫描结果
            {
                utuple = (UHeapTuple)oldslot->tts_tuple;  // 获取当前元组

                if (tab->rewrite > 0)  // 如果需要重写
                {
                    int newvals_num = 0;  // 新值的数量
                    /* 从旧元组中提取数据 */
                    tableam_tops_deform_tuple(utuple, oldTupDesc, values, isnull);

                    /*
                     * 处理提供的表达式来替换选定的列。
                     *
                     * 首先,评估输入来自旧元组的表达式。
                     */
                    econtext->ecxt_scantuple = oldslot;

                    foreach(l, tab->newvals)  // 遍历所有新值
                    {
                        NewColumnValue *ex = (NewColumnValue*)lfirst(l);

                        if (ex->is_addloc) {  // 如果是添加位置
                            for (i = oldTupDesc->natts + newvals_num - 1; i >= ex->attnum - 1; i--) {
                                values[i + 1] = values[i];
                                isnull[i + 1] = isnull[i];
                            }
                            newvals_num++;
                        }

                        if (ex->is_generated) {  // 如果是生成的列
                            if (tab->is_first_after) {
                                UpdateValueModifyFirstAfter(ex, values, isnull);
                                has_generated = true;
                            } else {
                                isnull[ex->attnum - 1] = true;
                            }
                            continue;
                        }

                        values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate, econtext, &isnull[ex->attnum - 1], NULL);

                        if (ex->is_autoinc) {  // 如果是自增列
                            need_autoinc = (autoinc_attnum > 0);
                        }

                        if (tab->is_first_after) {
                            UpdateValueModifyFirstAfter(ex, values, isnull);
                        }
                    }

                    /* 更新生成的列是否为空 */
                    UpdateGeneratedColumnIsnull(tab, isnull, has_generated);

                    /* 自增处理 */
                    if (need_autoinc) {
                        autoinc = EvaluateAutoIncrement(oldrel, newTupDesc,
                            autoinc_attnum, &values[autoinc_attnum - 1], &isnull[autoinc_attnum - 1]);
                    }

                    /* 设置已删除属性为 null */
                    foreach(lc, dropped_attrs) {
                        isnull[lfirst_int(lc)] = true;  // 遍历已删除属性列表,设置相应的 isnull 数组值为 true
                    }

					/* 形成新元组 */
                    utuple = (UHeapTuple)tableam_tops_form_tuple(newTupDesc, values, isnull, TableAmUstore);
                }

                 /* 检查新元组的约束 */
                (void)ExecStoreTuple(utuple, newslot, InvalidBuffer, false);
                econtext->ecxt_scantuple = newslot;

				/* 评估依赖新元组的表达式 */
                utuple = EvaluateGenExpr<UHeapTuple, TAM_USTORE>(tab, utuple, newTupDesc, econtext, values, isnull);

				/* 检查 NOT NULL 约束 */
                foreach(l, notnull_attrs)
                {
                    int attn = lfirst_int(l); // 获取属性索引

					/* 检查属性是否为 null */
                    if (tableam_tops_tuple_attisnull(utuple, attn + 1, newTupDesc))
                            ereport(ERROR,
                                    (errcode(ERRCODE_NOT_NULL_VIOLATION),
                                             errmsg("column \"%s\" contains null values",
                                                    NameStr(newTupDesc->attrs[attn].attname))));
                }
				
				/* 检查所有约束 */
                foreach(l, tab->constraints)
                {
                    NewConstraint *con = (NewConstraint*)lfirst(l); // 获取约束对象

                    ListCell* lc = NULL;

                    switch (con->contype)
                    {
                        case CONSTR_CHECK: // 检查约束
                        {
                        if (estate->es_is_flt_frame){ // 如果是平面执行
                            foreach (lc, con->qualstate) {
                                ExprState* exprState = (ExprState*)lfirst(lc);

								/* 执行检查 */
                                if (!ExecCheckByFlatten(exprState, econtext))
                                    ereport(ERROR,
                                        (errcode(ERRCODE_CHECK_VIOLATION),
                                             errmsg("check constraint \"%s\" is violated by some row",
                                                    con->name)));
                            }
                        } else {
                        	/* 普通检查 */
                            if (!ExecQual(con->qualstate, econtext, true)){
                                ereport(ERROR,
                                        (errcode(ERRCODE_CHECK_VIOLATION),
                                             errmsg("check constraint \"%s\" is violated by some row",
                                                    con->name)));
                            }
                        }
                        }
                            break;
                        case CONSTR_FOREIGN:
                            /* 外键约束,无需操作 */
                            break;
                        default:
                        {
                            ereport(ERROR,
                                        (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                                         errmsg("unrecognized constraint type: %d", (int) con->contype)));
                        }
                    }
                }

                /* 将元组写入新表 */
                if (newrel) {
                    (void)tableam_tuple_insert(newrel, utuple, mycid, hi_options, bistate); // 插入元组到新表,使用之前设置的命令ID和插入选项

                    if (autoinc > 0) {
                        SetRelAutoIncrement(oldrel, newTupDesc, autoinc); // 如果涉及自增字段,设置自增值
                    }
                }

                /*
                 * 在进入下一个循环前重置槽的标志,以便不会在上下文重置后清除它。
                 * 注意我们不显式释放元组,因为元组的内存上下文将很快被重置。
                 */
                oldslot->tts_flags &= ~TTS_FLAG_SHOULDFREE; // 清除槽的应释放标志

                UHeapTuple backUpTup = BackUpScanCuTup(((UHeapScanDesc) scan)->rs_cutup); // 备份当前扫描的元组
                ResetExprContext(econtext); // 重置表达式上下文
                ((UHeapScanDesc) scan)->rs_cutup = RestoreScanCuTup(backUpTup); // 恢复扫描的元组
                if (backUpTup != NULL) {
                    pfree_ext(backUpTup); // 释放备份的元组内存
                }

                CHECK_FOR_INTERRUPTS(); // 检查中断
            }
        } else {
            ((HeapScanDesc) scan)->rs_tupdesc = oldTupDesc; // 设置扫描描述符为旧表描述符
            while ((tuple =  (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) { // 循环扫描旧表元组
                if (tab->rewrite > 0) {
                    Oid tupOid = InvalidOid; // 初始化元组OID
                    int newvals_num = 0;

                    /* 从旧元组提取数据 */
                    tableam_tops_deform_tuple(tuple, oldTupDesc, values, isnull); // 解构元组数据
                    if (oldTupDesc->tdhasoid)
                        tupOid = HeapTupleGetOid(tuple); // 获取元组的OID

                    /*
                     * 处理提供的表达式以替换选定的列。
                     * 首先,评估输入来自旧元组的表达式。
                     */
                    (void)ExecStoreTuple(tuple, oldslot, InvalidBuffer, false); // 存储元组到旧槽
                    econtext->ecxt_scantuple = oldslot; // 设置执行上下文的扫描元组为旧槽

                    foreach (l, tab->newvals) {
                        NewColumnValue* ex = (NewColumnValue*)lfirst(l);

                        if (ex->is_addloc) {
                            for (i = oldTupDesc->natts + newvals_num - 1; i >= ex->attnum - 1; i--) {
                                values[i + 1] = values[i];
                                isnull[i + 1] = isnull[i];
                            }
                            newvals_num++;
                        }

                        if (ex->is_generated) {
                            if (tab->is_first_after) {
                                UpdateValueModifyFirstAfter(ex, values, isnull);
                                has_generated = true;
                            } else {
                                isnull[ex->attnum - 1] = true;
                            }
                            continue;
                        }

                        values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate, econtext, &isnull[ex->attnum - 1]);
                        if (ex->is_autoinc) {
                            need_autoinc = (autoinc_attnum > 0);
                        }

                        if (tab->is_first_after) {
                            UpdateValueModifyFirstAfter(ex, values, isnull);
                        }
                    }

                    /* 处理生成的列,如果有生成字段,设置相应的isnull标志 */
                    UpdateGeneratedColumnIsnull(tab, isnull, has_generated);

                    /* 自增列处理 */
                    if (need_autoinc) {
                        autoinc = EvaluateAutoIncrement(oldrel, newTupDesc,
                            autoinc_attnum, &values[autoinc_attnum - 1], &isnull[autoinc_attnum - 1]);  // 计算自增值
                    }

                    /* 将删除的属性设置为null */
                    foreach (lc, dropped_attrs) {
                        isnull[lfirst_int(lc)] = true;  // 遍历删除属性列表,设置isnull标志
                    }

                    /*
                     * 构造新元组,注意这里不需要显式释放内存,
                     * 因为元组的内存上下文将很快被重置。
                     */
                    tuple = (HeapTuple)heap_form_tuple(newTupDesc, values, isnull);  // 根据新描述符和值构造新元组

                    /* 如果存在OID,保留OID */
                    if (newTupDesc->tdhasoid)
                        HeapTupleSetOid(tuple, tupOid);  // 设置新元组的OID

                /* 检查可能更改的元组上的约束 */
                (void)ExecStoreTuple(tuple, newslot, InvalidBuffer, false);  // 存储新元组到新槽中
                econtext->ecxt_scantuple = newslot;  // 更新执行上下文中的扫描元组为新槽

                /*
                 * 评估依赖新元组的表达式。我们假设这些列不会相互引用,
                 * 因此没有排序依赖性。
                 */
                tuple = EvaluateGenExpr

                /* 遍历所有需要检查非空的属性 */
                foreach (l, notnull_attrs) {
                    int attn = lfirst_int(l);  // 获取当前属性的索引

                    /* 如果属性值为null,则抛出错误 */
                    if (relationAttIsNull(tuple, attn + 1, newTupDesc))
                        ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION),
                            errmsg("column \"%s\" contains null values", NameStr(newTupDesc->attrs[attn].attname))));
                }

                /* 遍历所有约束,进行检查 */
                foreach (l, tab->constraints) {
                    NewConstraint* con = (NewConstraint*)lfirst(l);  // 获取当前约束

                    /* 根据约束类型进行相应处理 */
                    switch (con->contype) {
                        case CONSTR_CHECK:  // 检查约束
                            {
                                if (estate->es_is_flt_frame){  // 如果是扁平化框架
                                    foreach (lc, con->qualstate) {
                                        ExprState* exprState = (ExprState*)lfirst(lc);

                                        /* 如果检查不通过,抛出错误 */
                                        if (!ExecCheckByFlatten(exprState, econtext))
                                            ereport(ERROR,
                                                (errcode(ERRCODE_CHECK_VIOLATION),
                                                    errmsg("check constraint \"%s\" is violated by some row",
                                                            con->name)));
                                    }
                                } else {
                                    /* 常规检查 */
                                    if (!ExecQualByRecursion(con->qualstate, econtext, true)){
                                        ereport(ERROR,
                                                (errcode(ERRCODE_CHECK_VIOLATION),
                                                    errmsg("check constraint \"%s\" is violated by some row",
                                                            con->name)));
                                    }
                                }
                            }
                            break;
                        case CONSTR_FOREIGN:
                            /* 外键约束,此处无需处理 */
                            break;
                        default: {
                            /* 无法识别的约束类型,抛出错误 */
                            ereport(ERROR,
                                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                                    errmsg("unrecognized constraint type: %d", (int)con->contype)));
                        }
                    }
                }

                /* 将元组写入新关系 */
                if (newrel) {
                    (void)tableam_tuple_insert(newrel, tuple, mycid, hi_options, bistate);  // 插入元组

                    /* 如果有自增值,设置自增 */
                    if (autoinc > 0) {
                        SetRelAutoIncrement(oldrel, newTupDesc, autoinc);
                    }
                }
                ResetExprContext(econtext);  // 重置表达式上下文

                CHECK_FOR_INTERRUPTS();  // 检查是否有中断请求
            }
        }

        MemoryContextSwitchTo(oldCxt);
        tableam_scan_end(scan);

        ExecDropSingleTupleTableSlot(oldslot);
        ExecDropSingleTupleTableSlot(newslot);
    }

    FreeExecutorState(estate);

    if (newrel) {
        FreeBulkInsertState(bistate);  // 释放批量插入状态资源

        /* 如果跳过了WAL写入,则需要同步堆 */
        if (((hi_options & TABLE_INSERT_SKIP_WAL) || enable_heap_bcm_data_replication()) &&
            !RelationIsSegmentTable(newrel))
            heap_sync(newrel);  // 如果设置了跳过WAL或启用了堆BCM数据复制,并且新关系不是分段表,则同步堆到磁盘

        /*
         * 重写临时表后,relfilenode会改变。
         * 我们需要找到带有新relfilenode的新TmptableCacheEntry。
         * 然后在新的TmptableCacheEntry中设置新的自增计数器值。
         */
        CopyTempAutoIncrement(oldrel, newrel);  // 复制旧表到新表的自增计数器值
    }
}