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

server/2024/9/19 23:01:03/ 标签: 数据库, postgresql, gaussdb

ALTER TABLE(ExecRewriteCStoreTable)

  • 概述
  • ExecRewriteCStoreTable 函数
    • ATCStoreRewriteTable 函数
    • ATCStoreGetRewriteAttrs 函数
    • ChangeTableSpaceForDeltaRelation 函数
    • ATOnlyCheckCStoreTable 函数

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

概述

  【OpenGauss源码学习 —— (ALTER TABLE(ExecRewriteRowTable))】这篇文章详细描述了 OpenGauss 数据库管理系统中执行 ALTER TABLE (ExecRewriteRowTable) 命令的源码实现。ExecRewriteCStoreTableExecRewriteRowTable 都是用于重写表的数据的函数,但是它们针对的表类型不同ExecRewriteCStoreTable 主要用于重写列存储表的数据,而 ExecRewriteRowTable 则用于重写行存储表的数据。因此,它们的关系在于它们都是用于表数据重写的功能,但是针对的表类型不同,分别适用于列存储表和行存储表
  本文,我们来学习一下 ExecRewriteCStoreTable 函数的内容。

ExecRewriteCStoreTable 函数

  ExecRewriteCStoreTable 函数的主要功能是重写列存储表的列关系数据,并在重写完成后重新构建表的索引。首先,通过表的 OID 打开待重写的原始堆表,然后调用 ATCStoreRewriteTable 函数来执行实际的重写操作,重写完成后关闭原始堆表。最后,通过调用 ReindexRelation 函数重新构建列存储表的所有索引,以确保索引与表数据的一致性。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp

/** @Description: 重写列存储表的列关系数据,并重新构建索引。* @Param[IN] lockmode: 重写数据时使用的锁模式* @Param[IN] NewTableSpace: 列存储表新的表空间* @Param[IN] tab: 修改表信息* @See also: ATCStoreRewriteTable*/
static void ExecRewriteCStoreTable(AlteredTableInfo* tab, Oid NewTableSpace, LOCKMODE lockmode)
{// 打开待重写的原始堆表Relation OldHeap = heap_open(tab->relid, NoLock);// 调用 ATCStoreRewriteTable 函数重写表ATCStoreRewriteTable(tab, OldHeap, lockmode, NewTableSpace);// 关闭原始堆表heap_close(OldHeap, NoLock);/* 然后,重新构建其索引。*/(void)ReindexRelation(tab->relid, REINDEX_REL_SUPPRESS_INDEX_USE | REINDEX_REL_CHECK_CONSTRAINTS, REINDEX_ALL_INDEX, NULL);
}

ATCStoreRewriteTable 函数

  ATCStoreRewriteTable 函数是用于重写列存储表数据的函数。它根据传入的 AlterTableInfo 结构体、旧的堆关系锁模式以及目标表空间对列存储表进行数据重写。函数首先根据表结构变更信息和表空间变更情况,决定是否需要重新分配表空间。然后,根据表结构变更信息,重新计算表达式设置 NOT NULL 约束。接着,使用列存储重写器,根据指定的规则,逐列对数据进行重写。在这个过程中,还需要处理与 Delta 关系CU 描述符关系CU 描述符索引关系相关的锁定和表空间变更。最后,完成数据重写后,对相应的表和索引进行更新,确保变更的可见性,并释放相关的资源。
  这个函数的执行流程可以分为以下几个步骤:

  1. 初始化变量: 函数开始时,初始化了一系列变量,包括新旧 TupleDesc重写信息数组重写标志数组等。
  2. 获取重写信息: 根据传入的 AlterTableInfo 结构体,获取需要重写的列信息,包括是否添加列设置数据类型等。
  3. 设置表达式和约束: 对于需要添加的列,函数会为其设置计算表达式,并根据需要设置 NOT NULL 约束
  4. 准备重写器: 创建一个列存储重写器,该重写器用于实际执行数据重写操作。
  5. 处理表空间变更: 如果目标表空间与当前表空间不同,则需要将表数据移动到新的表空间
  6. 重写列数据: 根据重写器的设定,逐列对表数据进行重写操作。这包括根据表达式计算新列值复制旧数据以及应用新的约束等。
  7. 更新元数据: 完成数据重写后,需要更新表索引的元数据信息,确保变更对外可见。
  8. 释放资源: 释放申请的内存和关闭相关的数据库连接。

  整个流程确保了列存储表数据的正确重写,并处理了表空间变更等特殊情况,以确保数据库操作的完整性和一致性。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp

/** @Description: 重写列存储表的数据。该函数根据传入的 AlterTableInfo 结构体,旧堆关系,锁模式以及目标表空间来重写列存储表的数据。* @Param[IN] tab: 修改表信息结构体,包含了进行表变更的所有信息。* @Param[IN] oldHeapRel: 旧的堆关系,即需要被重写的列存储表。* @Param[IN] lockMode: 在重写数据期间使用的锁模式。* @Param[IN] targetTblspc: 新的目标表空间,用于重写后的列存储表。* @See also: ATCStoreGetRewriteAttrs, CStoreRewriter, CStoreCopyColumnData*/
static void ATCStoreRewriteTable(AlteredTableInfo* tab, Relation oldHeapRel, LOCKMODE lockMode, Oid targetTblspc)
{bool tblspcChanged = NeedToSetTableSpace(oldHeapRel, targetTblspc); // 检查是否需要更改表空间Oid newfilenode = InvalidOid; // 新文件节点Oid cudescOid = InvalidOid; // CU 描述符关系的对象标识符Oid cudescIdxOid = InvalidOid; // CU 描述符索引关系的对象标识符Relation cudescRel = NULL; // CU 描述符关系Relation pg_class = NULL; // pg_class 关系Relation CUReplicationRel = NULL; // 用于复制 CU 的关系HeapTuple pgclass_tuple = NULL; // pg_class 元组Form_pg_class pgclass_form = NULL; // pg_class 表的形式CStoreRewriter* rewriter = NULL; // 列存储重写器errno_t rc;/** 注意:旧的 TupleDesc 已经被复制并保存在 tab->oldDesc 中。*   现在新的 TupleDesc 可以在 oldHeapRel 中找到并使用。*/TupleDesc newTupDesc = RelationGetDescr(oldHeapRel); // 获取新的 TupleDescTupleDesc oldTupDesc = tab->oldDesc; // 获取旧的 TupleDescAssert(newTupDesc->natts >= oldTupDesc->natts); // 断言新的 TupleDesc 的属性数量大于等于旧的 TupleDesc 的属性数量/* 不支持的表/列约束:CHECK、FOREIGN KEY */Assert(tab->constraints == NIL); // 断言约束为空int nColsOfEachType[CSRT_NUM]; // 每种类型的列数数组rc = memset_s(nColsOfEachType, sizeof(int) * CSRT_NUM, 0, sizeof(int) * CSRT_NUM); // 初始化数组为零securec_check(rc, "", "");int maxCols = newTupDesc->natts; // 最大列数bool* rewriteFlags = (bool*)palloc0(sizeof(bool) * maxCols); // 重写标志数组CStoreRewriteColumn** rewriteInfo = (CStoreRewriteColumn**)palloc0(sizeof(void*) * maxCols); // 重写信息数组/* 拆分出:ADD COLUMN、SET DATA TYPE COLUMN 和其他情况 */ATCStoreGetRewriteAttrs(tab, oldTupDesc, newTupDesc, rewriteInfo, rewriteFlags, nColsOfEachType); // 获取重写属性/* 为更新的列设置重新计算表达式 */ListCell* l = NULL;foreach (l, tab->newvals) {NewColumnValue* ex = (NewColumnValue*)lfirst(l); // 获取每个新列的值ex->exprstate = ExecInitExpr((Expr*)ex->expr, NULL); // 初始化表达式状态/* 我们预期每个属性只有一个 NewColumnValue */Assert(rewriteInfo[ex->attnum - 1] != NULL); // 断言重写信息不为空Assert(rewriteInfo[ex->attnum - 1]->newValue == NULL); // 断言新值为空ColumnNewValue* newValExp = (ColumnNewValue*)palloc(sizeof(ColumnNewValue)); // 分配新的列值结构体内存newValExp->expr = ex->expr; // 设置表达式newValExp->exprstate = ex->exprstate; // 设置表达式状态rewriteInfo[ex->attnum - 1]->newValue = newValExp; // 将新值存入重写信息数组}/* 为更新的列设置 NOT NULL 约束 */if (tab->rewrite > 0 || tab->new_notnull) {for (int i = 0; i < maxCols; ++i) {if (rewriteInfo[i] != NULL && !rewriteInfo[i]->isDropped && newTupDesc->attrs[i].attnotnull) {rewriteInfo[i]->notNull = true; // 设置非空约束}}}/* 重写列存储表 */rewriter = New(CurrentMemoryContext) CStoreRewriter(oldHeapRel, oldTupDesc, newTupDesc); // 创建列存储重写器/* 锁顺序:* 1. 列关系* 2. Delta 关系 [ Delta 索引关系 ]* 3. Cudesc 关系 + Cudesc 索引关系*/if (OidIsValid(oldHeapRel->rd_rel->reldeltarelid)) { // 如果 Delta 关系的对象标识符有效LockRelationOid(oldHeapRel->rd_rel->reldeltarelid, lockMode); // 锁定 Delta 关系}cudescOid = oldHeapRel->rd_rel->relcudescrelid; // 获取 CU 描述符关系的对象标识符cudescRel = heap_open(cudescOid, lockMode); // 打开 CU 描述符关系cudescIdxOid = cudescRel->rd_rel->relcudescidx; // 获取 CU 描述符索引关系的对象标识符LockRelationOid(cudescIdxOid, lockMode); // 锁定 CU 描述符索引关系if (tblspcChanged) { // 如果表空间发生了改变/* 处理 Delta 和 Delta 索引关系* 现在可以直接通过块复制关系数据,因为 Delta 关系现在不可用且没有数据。** 当 Delta 关系可用时,必须扫描其中的所有元组,重写并合并/追加到 CU 文件中*/ChangeTableSpaceForDeltaRelation(oldHeapRel->rd_rel->reldeltarelid, targetTblspc, lockMode); // 修改 Delta 关系的表空间/* 处理每个列的数据 *//* 在这里可以安全地打开 pg_class 关系,因为:* 1. 不能更改分区表的表空间;* 2. 不能为一个分区表添加列/设置列数据类型;* 3. 更改一个分区表的表空间不会触发此分支;* 因此它是一个普通的 pg_class 关系。*/pg_class = heap_open(RelationRelationId, RowExclusiveLock); // 打开 pg_class 关系/* 获取关系的可修改副本 */pgclass_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(tab->relid)); // 获取关系的元组if (!HeapTupleIsValid(pgclass_tuple)) { // 如果元组无效ereport(ERROR,(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", tab->relid)));}pgclass_form = (Form_pg_class)GETSTRUCT(pgclass_tuple); // 获取表的形式/** Relfilenodes 在表空间中不是唯一的,因此我们需要在新的表空间中分配一个新的。*/newfilenode = GetNewRelFileNode(targetTblspc, NULL, oldHeapRel->rd_rel->relpersistence); // 获取新的文件节点RelFileNode CUReplicationFile = {ConvertToRelfilenodeTblspcOid(targetTblspc), oldHeapRel->rd_node.dbNode, newfilenode, InvalidBktId};CUReplicationRel = CreateCUReplicationRelation(CUReplicationFile,oldHeapRel->rd_backend,oldHeapRel->rd_rel->relpersistence,RelationGetRelationName(oldHeapRel)); // 创建 CU 复制关系for (int i = 0; i < maxCols; ++i) {Form_pg_attribute thisattr = &newTupDesc->attrs[i]; // 获取当前属性/* 跳过已删除和重写的列 */if (!thisattr->attisdropped && !rewriteFlags[i]) {CStoreCopyColumnData(CUReplicationRel, oldHeapRel, thisattr->attnum); // 复制列数据}}/* 准备处理 ADD COLUMN 和 SET DATA TYPE COLUMN */rewriter->ChangeTableSpace(CUReplicationRel); // 修改表空间}/* 处理 Cudesc 关系 + ADD COLUMN + SET DATA TYPE COLUMN */rewriter->BeginRewriteCols(maxCols, rewriteInfo, nColsOfEachType, rewriteFlags); // 开始重写列rewriter->RewriteColsData(); // 重写列数据rewriter->EndRewriteCols(); // 结束重写列DELETE_EX(rewriter); // 释放重写器内存if (tblspcChanged) { // 如果表空间发生了改变CStoreCopyColumnDataEnd(oldHeapRel, targetTblspc, newfilenode); // 结束列数据复制/* 销毁虚假关系 */FreeFakeRelcacheEntry(CUReplicationRel); // 释放虚假关系/* 更新 pg_class 行 */pgclass_form->reltablespace = ConvertToPgclassRelTablespaceOid(targetTblspc); // 更新表空间pgclass_form->relfilenode = newfilenode; // 更新文件节点simple_heap_update(pg_class, &pgclass_tuple->t_self, pgclass_tuple); // 简单堆更新CatalogUpdateIndexes(pg_class, pgclass_tuple); // 更新索引tableam_tops_free_tuple(pgclass_tuple); // 释放元组内存heap_close(pg_class, RowExclusiveLock); // 关闭 pg_class 关系/* 确保 reltablespace 变更可见 */CommandCounterIncrement(); // 增加命令计数器/* 处理 Cudesc 索引关系 */ATExecSetTableSpace(cudescIdxOid, ConvertToRelfilenodeTblspcOid(targetTblspc), lockMode); // 修改表空间}/* 直到提交之前都解锁 */heap_close(cudescRel, NoLock); // 关闭 CU 描述符关系/* 最后清理工作 */for (int k = 0; k < maxCols; ++k) {if (rewriteInfo[k]) {CStoreRewriteColumn::Destroy(&rewriteInfo[k]); // 销毁重写信息}}pfree_ext(rewriteInfo); // 释放重写信息内存pfree_ext(rewriteFlags); // 释放重写标志数组内存
}

  下面针对以上函数中的部分代码段进行解释。


 	/* 为更新的列设置重新计算表达式 */ListCell* l = NULL;foreach (l, tab->newvals) {NewColumnValue* ex = (NewColumnValue*)lfirst(l); // 获取每个新列的值ex->exprstate = ExecInitExpr((Expr*)ex->expr, NULL); // 初始化表达式状态/* 我们预期每个属性只有一个 NewColumnValue */Assert(rewriteInfo[ex->attnum - 1] != NULL); // 断言重写信息不为空Assert(rewriteInfo[ex->attnum - 1]->newValue == NULL); // 断言新值为空ColumnNewValue* newValExp = (ColumnNewValue*)palloc(sizeof(ColumnNewValue)); // 分配新的列值结构体内存newValExp->expr = ex->expr; // 设置表达式newValExp->exprstate = ex->exprstate; // 设置表达式状态rewriteInfo[ex->attnum - 1]->newValue = newValExp; // 将新值存入重写信息数组}

  假设我们有一个表 employee,需要对其进行 ALTER TABLE 操作,添加一个新列 salary,并为该列设置默认值。现在假设我们使用以下 SQL 语句进行操作:

ALTER TABLE employee ADD COLUMN salary INTEGER DEFAULT 50000;

  在数据库系统内部,这个操作会被解析成一系列的操作步骤,其中就包括对新添加的列 salary 进行默认值的设置。这时候,就涉及到了如何计算并设置默认值的问题,而上述代码片段就是用来处理这个问题的。
  假设我们的 AlteredTableInfo 结构体中有一个 tab->newvals 的列表,其中包含了新添加的列信息,比如列名默认值等。这个列表是一个存储了新列信息的数据结构
  上述代码片段的作用就是遍历 tab->newvals 列表对其中的每个新列进行处理。对于每个新列,首先会初始化其表达式状态,然后将表达式和其状态保存到一个新的结构体 ColumnNewValue 中,并将该结构体存入 rewriteInfo 数组中的相应位置。
  举例来说,假设我们要添加的新列 salary 的默认值为 50000,那么这段代码就会将这个默认值表达式以及其状态存入 rewriteInfo 数组中的 salary 对应位置。这样,在后续的操作中,数据库系统就可以根据这些信息来计算并设置新列的默认值。


RelFileNode CUReplicationFile = {ConvertToRelfilenodeTblspcOid(targetTblspc), oldHeapRel->rd_node.dbNode, newfilenode, InvalidBktId};
CUReplicationRel = CreateCUReplicationRelation(CUReplicationFile,oldHeapRel->rd_backend,oldHeapRel->rd_rel->relpersistence,RelationGetRelationName(oldHeapRel)); // 创建 CU 复制关系

  以上代码的作用是创建一个用于复制列存表数据的 CUColumn Unit)复制关系。具体来说,代码中首先构建了一个 RelFileNode 结构体 CUReplicationFile,用于表示新创建的 CU 复制关系的物理文件信息。其中,ConvertToRelfilenodeTblspcOid(targetTblspc) 用于获取目标表空间的 RelFileNode 中的表空间 OIDoldHeapRel->rd_node.dbNode 表示原始列存表的数据库 OIDnewfilenode新的 CU 复制关系的文件节点 OIDInvalidBktId 则表示无效的桶 ID
  然后,利用这个文件节点信息,调用 CreateCUReplicationRelation 函数来创建一个 CU 复制关系。这个函数需要传入以下参数:

  • CUReplicationFile: 表示新创建的 CU 复制关系的物理文件信息。
  • oldHeapRel->rd_backend: 表示原始列存表的后端 ID,用于关联新的 CU 复制关系。
  • oldHeapRel->rd_rel->relpersistence: 表示原始列存表的持久性,用于设置新的 CU 复制关系的持久性。
  • RelationGetRelationName(oldHeapRel): 表示原始列存表的名称,用于设置新的 CU 复制关系的名称。

ATCStoreGetRewriteAttrs 函数

  ATCStoreGetRewriteAttrs 函数的功能是在执行 ALTER TABLE 操作时获取需要检查或重写的所有属性信息。它遍历了 ALTER TABLE 命令的两个阶段添加列修改列数据类型),并根据命令的类型在新旧 Tuple 描述之间进行匹配,以确定哪些属性需要进行重写操作。

  • 对于添加列的操作,函数首先检查命令的定义,确保它是列定义。然后,它倒序遍历新 Tuple 描述的属性,以尽可能减少循环次数。对于每个属性,它检查列名是否与要添加的列名匹配,如果匹配,则创建用于添加列的重写信息对象,并记录要添加的新列数
  • 对于修改列数据类型的操作,函数遍历旧 Tuple 描述的所有属性,查找与要修改数据类型的列名匹配的属性。如果找到匹配项,则创建用于修改数据类型的重写信息对象,并记录要修改数据类型的列数

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

// 获取需要检查或重写的所有属性。
//
static void ATCStoreGetRewriteAttrs(_in_ AlteredTableInfo* tab, _in_ TupleDesc oldTupDesc, _in_ TupleDesc newTupDesc,_out_ CStoreRewriteColumn** rewriteInfo, _out_ bool* rewriteFlags, _out_ int* nColsOfEachType)
{// 断言新 Tuple 描述的属性数量不小于旧 Tuple 描述的属性数量。Assert(newTupDesc->natts >= oldTupDesc->natts);// 断言重写标志和每种类型的列数不为空。Assert(nColsOfEachType && rewriteFlags);// 遍历 ALTER TABLE 命令的两个执行阶段:添加列和修改列数据类型。for (int pass = 0; pass < AT_NUM_PASSES; ++pass) {// 如果当前阶段的子命令列表为空,则跳过。if (tab->subcmds[pass] == NIL) {continue;}// 获取当前阶段的子命令列表。List* subcmds = tab->subcmds[pass];ListCell* cmdCell = NULL;// 遍历子命令列表中的每个 ALTER TABLE 命令。foreach (cmdCell, subcmds) {AlterTableCmd* cmd = (AlterTableCmd*)lfirst(cmdCell);// 添加列。if (pass == AT_PASS_ADD_COL) {// 断言命令定义存在且类型为列定义。Assert(cmd->def && cmd->def->type == T_ColumnDef);ColumnDef* colDef = (ColumnDef*)cmd->def;// 断言列名存在且不为空。Assert(colDef->colname && colDef->colname[0] != '\0');// 倒序搜索新 Tuple 描述的属性,以尽可能减少循环次数。for (int attrIdx = newTupDesc->natts - 1; attrIdx >= 0; --attrIdx) {// 如果找到与要添加列名匹配的属性。if (pg_strcasecmp(colDef->colname, RelAttrName(newTupDesc, attrIdx)) == 0) {// 断言重写信息不存在。Assert(rewriteInfo[attrIdx] == NULL);// 创建用于添加列的重写信息对象。rewriteInfo[attrIdx] = CStoreRewriteColumn::CreateForAddColumn(attrIdx + 1);// 记录要添加的新列数。++nColsOfEachType[CSRT_ADD_COL];// 设置重写标志为真。rewriteFlags[attrIdx] = true;break;}}continue;}// 修改列数据类型。if (pass == AT_PASS_ALTER_TYPE) {// 断言列名存在且不为空。Assert(cmd->name && cmd->name[0] != '\0');// 遍历旧 Tuple 描述的所有属性。for (int attrIdx = 0; attrIdx < oldTupDesc->natts; ++attrIdx) {// 如果找到与要修改数据类型的列名匹配的属性。if (pg_strcasecmp(cmd->name, RelAttrName(oldTupDesc, attrIdx)) == 0) {// 禁止对同一列进行多次 ALTER TYPE。Assert(rewriteInfo[attrIdx] == NULL);// 创建用于修改数据类型的重写信息对象。rewriteInfo[attrIdx] = CStoreRewriteColumn::CreateForSetDataType(attrIdx + 1);// 记录要修改数据类型的列数。++nColsOfEachType[CSRT_SET_DATA_TYPE];// 设置重写标志为真。rewriteFlags[attrIdx] = true;break;}}continue;}}}
}

注释: AT_NUM_PASSES 是一个宏定义,表示 ALTER TABLE 命令执行的不同阶段的数量。在 OpenGauss 中,ALTER TABLE 命令通常会分为多个阶段执行,比如添加列删除列修改列数据类型等。这个宏定义的值指示了 ALTER TABLE 命令的总执行阶段数

ChangeTableSpaceForDeltaRelation 函数

  ChangeTableSpaceForDeltaRelation 函数实现了一个用于更改 Delta 关系表空间的函数Delta 关系通常用于存储基表发生变化时的增量数据。该函数首先检查提供的 Delta 关系对象标识符是否有效,然后将目标表空间转换为有效表空间,并使用给定的锁模式打开 Delta 关系。接着,通过调用 ATExecSetTableSpace 函数来改变 Delta 关系的表空间。最后,解锁 Delta 关系并处理 Delta 索引关系的表空间改变(代码中未给出)。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp

/** @Description: 为 Delta 关系改变表空间。* @Param[IN] deltaOid: Delta 关系的对象标识符。* @Param[IN] lockmode: 在改变表空间过程中使用的锁模式。* @Param[IN] targetTableSpace: 新的表空间。* @See also:*/
static inline void ChangeTableSpaceForDeltaRelation(Oid deltaOid, Oid targetTableSpace, LOCKMODE lockmode)
{// 如果 deltaOid 是有效的if (OidIsValid(deltaOid)) {/* ATExecSetTableSpace() 需要 targetTableSpace 不是 InvalidOid */targetTableSpace = ConvertToRelfilenodeTblspcOid(targetTableSpace);// 断言 targetTableSpace 是有效的Assert(OidIsValid(targetTableSpace));// 使用给定的锁模式打开 delta 关系Relation deltaRel = heap_open(deltaOid, lockmode);// 改变 Delta 关系的表空间ATExecSetTableSpace(deltaOid, targetTableSpace, lockmode);// 直到提交前解锁relation_close(deltaRel, NoLock);// 改变 Delta 索引关系的表空间}
}

ATOnlyCheckCStoreTable 函数

  函数的作用是对列存储表进行非空约束的检查。它首先检查是否有新的非空约束,如果,则获取所有非空属性,并标记需要进行扫描。然后,它通过列存储扫描器进行扫描,逐行检查非空约束是否满足。如果发现某一行的某一非空属性为空值,则抛出错误。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp

/** ATOnlyCheckCStoreTable* Only support not-null constraint check currently.*//** ATOnlyCheckCStoreTable* 目前仅支持非空约束检查。*/static void ATOnlyCheckCStoreTable(const AlteredTableInfo* tab, Relation rel)
{// 旧的元组描述符和新的元组描述符TupleDesc oldTupDesc = NULL;TupleDesc newTupDesc = NULL;// 非空属性列表和是否需要扫描的标志List* notnullAttrs = NIL;bool needscan = false;// 获取旧的元组描述符和新的元组描述符oldTupDesc = tab->oldDesc;newTupDesc = RelationGetDescr(rel); /* 包含所有的修改 */// 如果有约束或新值,则报错,因为列存储关系不支持这个特性if (tab->constraints != NIL || tab->newvals != NIL) {ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),errmsg("Un-support feature"),errdetail("column stored relation doesn't support this feature")));}// 如果有新的非空约束,则检查所有非空约束if (tab->new_notnull) {/** 如果我们添加了任何新的非空约束,则检查所有非空约束。*/for (int i = 0; i < newTupDesc->natts; i++) {if (newTupDesc->attrs[i].attnotnull && !newTupDesc->attrs[i].attisdropped)notnullAttrs = lappend_int(notnullAttrs, i);}if (notnullAttrs != NULL)needscan = true;}// 如果需要扫描,则进行扫描检查非空约束if (needscan) {// 列扫描器和向量扫描批次CStoreScanDesc cstoreScan = NULL;VectorBatch* vecScanBatch = NULL;// 属性数量和属性索引数组int attrNum = list_length(notnullAttrs);AttrNumber* colIdx = (AttrNumber*)palloc(sizeof(AttrNumber) * attrNum);int n = 0;// 矢量和非空属性列表的迭代器ListCell* cell = NULL;ScalarVector *vec = NULL;// 遍历非空属性列表,构建属性索引数组foreach (cell, notnullAttrs) {colIdx[n++] = lfirst_int(cell) + 1;}// 打印调试信息,验证表ereport(DEBUG1, (errmsg("verifying table \"%s\"", RelationGetRelationName(rel))));/** 通过列存扫描,如果需要则生成新的行,然后检查约束。*/cstoreScan = CStoreBeginScan(rel, attrNum, colIdx, SnapshotNow, false);do {vecScanBatch = CStoreGetNextBatch(cstoreScan);vec = vecScanBatch->m_arr;for (int rowIdx = 0; rowIdx < vecScanBatch->m_rows; rowIdx++) {for (n = 0; n < attrNum; ++n ) {int attn = colIdx[n] - 1;if (vec[attn].IsNull(rowIdx)) {ereport(ERROR,(errcode(ERRCODE_NOT_NULL_VIOLATION),errmsg("column \"%s\" contains null values", NameStr(newTupDesc->attrs[attn].attname))));}}}} while (!CStoreIsEndScan(cstoreScan));CStoreEndScan(cstoreScan);pfree_ext(colIdx);}// 释放非空属性列表的内存list_free_ext(notnullAttrs);
}

http://www.ppmy.cn/server/40664.html

相关文章

SpringBoot整合JSR303分组校验

文章目录 JSR303 校验&#xff1a;分组校验 &#xff1a; JSR303 校验&#xff1a; 分组校验 &#xff1a; 首先导入依赖&#xff1a; <!-- JSR303 校验--><dependency><groupId>javax.validation</groupId><artifactId>validatio…

Spring6 的JdbcTemplate的JDBC模板类的详细使用说明

1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明 文章目录 1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明每博一文案2. 环境准备3. 数据准备4. 开始4.1 从数据表中插入(添加)数据4.2 从数据表中修改数据4.3 从数据表中删除数据4.4 从数据表中查询一个对象4.5 从数…

Mysql 基础 - 常见 子句

算数运算符 > < > < !/<> 逻辑运算符 3i in is null is not null 2l limit like 2o or 、order by 1a and ib between and 1n not and、or 、not、 in、 orderby、 limit、 like、 between...and、 is null 、is not null

docker 部署SSM项目(包含打包)

一&#xff1a;SSM项目打包 1.这个一定要勾选防止静态资源没打包上 2.第二步 3.第三步 4.更改名字(注意部署到线上的时候这里如果用docker或者window部署的话需要带这个项目名&#xff0c;不然会出现找不到接口的情况) ![在这里插入图片描述](https://img-blog.csdnimg.cn/dir…

Vue.js

1.过渡 Vue 在插入、更新或者移除 DOM 时&#xff0c;提供多种不同方式的应用过渡效果。 Vue 提供了内置的过渡封装组件&#xff0c;该组件用于包裹要实现过渡效果的组件。 语法格式 <transition name "nameoftransition"> <div></div> <…

2024最新版JavaScript逆向爬虫教程-------基础篇之无限debugger的原理与绕过

目录 一、无限debugger的原理与绕过1.1 案例介绍1.2 实现原理1.3 绕过debugger方法1.3.1 禁用所有断点1.3.2 禁用局部断点1.3.3 替换文件1.3.4 函数置空与hook 二、补充2.1 改写JavaScript文件2.2 浏览器开发者工具中出现的VM开头的JS文件是什么&#xff1f; 三、实战 一、无限…

华为交换机SSH配置示例

华为交换机支持的SSH和ACL具有关键的安全和配置要求&#xff0c;以确保网络交互的安全性和高效管理。 一、SSH在华为交换机中的原理和配置 SSH技术原理&#xff1a; SSH&#xff08;Secure Shell&#xff09;是一种加密的网络协议&#xff0c;用于在不安全的网络上安全地进行…

电力场景设备漏油检测数据集VOC+YOLO格式338张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;338 标注数量(xml文件个数)&#xff1a;338 标注数量(txt文件个数)&#xff1a;338 标注类别…

两种方法合并3dtiles(分别使用js/java)

目录 前言&#xff1a; 需合并的json目录 aa/tileset.json bb/tileset.json cc/tileset.json dd/tileset.json ee/tileset.json js源码&#xff1a; 运行命令&#xff1a; 生成结果&#xff1a; java源码&#xff1a; Matrix.java ThreeDTilesJoin2.java pom文件…

绘制奇迹:Processing中的动态图形与动画

&#x1f680; 欢迎回到Processing的世界&#xff0c;你的艺术编程航程刚刚开始。在我们的入门篇中&#xff0c;你已经学会了如何用Processing绘制基本的静态图形。现在&#xff0c;让我们一起探索Processing强大的动态图形和动画功能&#xff0c;释放你的创造力&#xff0c;走…

python跟C++选哪个?

选择使用Python还是C取决于你的具体需求和项目背景。我这里有一套编程入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习编程&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信22&#xff0c;我在后台发给你。 在通信工程行业…

HCIP(BGP综合实验)--8

一&#xff1a;实验要求 二&#xff1a;实现过程 &#xff08;一&#xff09;配置IP地址&#xff1a; AR1: [AR1]int g0/0/0 [AR1-GigabitEthernet0/0/0]ip add 12.1.1.1 24 [AR1-GigabitEthernet0/0/0]int l0 [AR1-LoopBack0]ip add 172.16.0.1 32 [AR1-LoopBack0]int l1 […

Cpython 的使用

前言 Python 程序中一些关键代码不想公开&#xff0c;可以使用 Cpython 来编译&#xff0c;然后正常导入 开始 需要明确一点&#xff0c;CPython 是 Python 的默认和最常用的解释器实现&#xff0c;而 .pyd 文件是一种特定于 Windows 的 Python 动态链接库&#xff08;DLL&a…

解决java.lang.IllegalArgumentException异常的正确方法

java.lang.IllegalArgumentException 是 Java 中的一个异常类&#xff0c;表示方法中传递的参数不合法。这个异常通常在方法被调用时抛出&#xff0c;表明方法的参数出现了问题。要正确解决这个异常&#xff0c;你可以按照以下步骤进行&#xff1a; 查看异常信息&#xff1a;首…

数据结构之排序(上)

片头 嗨&#xff0c;小伙伴们&#xff0c;大家好&#xff01;我们今天来学习数据结构之排序&#xff08;上&#xff09;&#xff0c;今天我们先讲一讲3个排序&#xff0c;分别是直接插入排序、冒泡排序以及希尔排序。 1. 排序的概念及其应用 1.1 排序的概念 排序&#xff1a…

JVM调优:JVM常用调优命令和参数

JVM调优是调整和配置Java虚拟机&#xff08;JVM&#xff09;的过程&#xff0c;以便最大限度地提高应用程序的性能和效率。这涉及到调整内存设置、选择合适的垃圾收集器&#xff0c;以及配置各种性能参数。 今天我们来了解一下参数如何设置 JVM调优的重要性 1.通过调整JVM参数…

Linux-笔记 常用命令

&#xff08;持续更新&#xff09; 1、压缩&#xff1a; tar -vcjf test1.tar.bz2 test1 tar -vczf test1.tar.gz test1 2、解压 tar -vxjf test1.tar.bz2 tar -vxzf test2.tar.gz 3、查找 find [路径] [参数] [文件名] : find / -name test* grep [参数] 关键字 路径&a…

深度学习UNet网络

DDPM主干模型&#xff1b; UNet是一种分类网络架构&#xff0c;输入一张图片&#xff0c;网络进行分类是目标物体还是背景像素&#xff1f; 像素级的判断。 最终输出是单通道388*388 但是输入是572&#xff0c;输入572是填充过来的 而且UNet使用的是镜像填充 镜像填充目的是…

计算机毕业设计python+spark知识图谱音乐推荐系统 音乐数据分析可视化大屏 音乐爬虫 LSTM情感分析 大数据毕设 深度学习 机器学习

本科毕业设计&#xff08;论文&#xff09;开题报告 课题名称 基于Spark的音乐推荐与数据分析系统 的设计与实现 课题类型 系统设计 学院 大数据与人工智能学院 班级 专业 数据科学与大数据技术 指导教师 职称 学生姓名 学号 重庆工程学院教务处制 1.课…

Windows下安装 Emscripten 详细过程

背景 最近研究AV1编码标准的aom编码器&#xff0c;编译的过程中发现需要依赖EMSDK&#xff0c;看解释EMSDK就是Emscripten 的相应SDK&#xff0c;所以此博客记录下EMSDK的安装过程&#xff1b;因为之前完全没接触过Emscripten 。 Emscripten Emscripten 是一个用于将 C 和 …