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

devtools/2024/9/23 20:30:43/

ALTER TABLE(ATRewriteTable)

  • 概述
  • ExecRewriteRowTable 函数
    • ATRewriteTable 函数
    • ATRewriteTableInternal 函数

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 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);// 创建一个新的堆表,返回新表的 OIDOid 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);// 清除所有的 attrinitdefvalclearAttrInitDefVal(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))));elseereport(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;  // 设置该列的空值标识为falsevalues[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; // 初始化元组OIDint 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);  // 复制旧表到新表的自增计数器值}
}

http://www.ppmy.cn/devtools/28140.html

相关文章

机器人技术概述_2.机器人4.0的核心技术

机器人4.0主要有以下几个核心技术&#xff1a;包括云-边-端的无缝协同计算、持续学习与协同学习、知识图谱、场景自适应和数据安全。 1.云-边-端的无缝协同计算 由于目前网络带宽和延迟的制约&#xff0c;当前机器人主要采用以机器人本身进行运算为主&#xff0c;云端处理非实…

Anaconda-用conda创建python虚拟环境常用命令

查看安装了哪些包 conda list查看当前存在哪些虚拟环境 conda env list conda info -e检查更新当前conda conda update condaPython创建虚拟环境 conda create -n your_env_name pythonx.xanaconda命令创建python版本为x.x&#xff0c;名字为your_env_name的虚拟环境。you…

6.k8s中的secrets资源-初识secret

目录 一、Secret 二、创建secrets资源 1.创建工作目录 2.尝试使用base64进行编码 3.声明式创建secrets资源 4.响应式创建secret 三、pod引用secret资源 1.pod资源env环境变量引用 2.pod资源volume存储卷引用secret资源 3.pod资源清单指定key引用secret 四、secret类型…

1. 深度学习笔记--神经网络中常见的激活函数

1. 介绍 每个激活函数的输入都是一个数字&#xff0c;然后对其进行某种固定的数学操作。激活函数给神经元引入了非线性因素&#xff0c;如果不用激活函数的话&#xff0c;无论神经网络有多少层&#xff0c;输出都是输入的线性组合。激活函数的意义在于它能够引入非线性特性&am…

iOS 获取到scrollView停止拖动时候的速度

在 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { self.velocity velocity.y; } 方法中得到手势离开的时候的速度 - (void)scrollViewWillEndDragging:(UIScro…

nginx--配置文件

组成 主配置文件&#xff1a;nginx.conf 子配置文件&#xff1a;include conf.d/*.conf 协议相关的配置文件&#xff1a;fastcgi uwsgi scgi等 mime.types&#xff1a;⽀持的mime类型&#xff0c;MIME(Multipurpose Internet Mail Extensions)多用途互联⽹网邮件扩展类型&…

ASP.NET通用作业批改系统设计

摘  要 该系统采用B/S结构&#xff0c;以浏览器方式登陆系统&#xff0c;用ASP.NET作为开发语言&#xff0c;数据库则使用Microsoft SQL Server 2000实现。《通用作业批改系统》包括了学生子系统、教师子系统、管理员子系统三大模块&#xff0c;该系统主要完成学生&#xff…

图床搭建GitHub+PicGo+jsdelivr(CDN)+Typora(内附加速工具)

目录 安装PicGo GitHub配置与加速器 配置PicGo 使用typroa 安装PicGo PicGo是一个用于上传图片的客户端&#xff0c;支持拖拽上传、剪贴板上传&#xff0c;功能十分方便。 下载地址&#xff1a; https://github.com/Molunerfinn/PicGo/releases 个人网盘自取版本2.4.0…