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

ALTER TABLE(ExecRewriteCStoreTable)

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

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 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); // 获取新的 TupleDesc
    TupleDesc oldTupDesc = tab->oldDesc; // 获取旧的 TupleDesc
    Assert(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 索引关系的表空间
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/606118.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IP SSL证书申请教程:实现HTTPS加密访问

随着网络安全意识的提高&#xff0c;HTTPS加密访问已经成为网站安全性的重要标准。通过安装SSL证书&#xff0c;网站可以实现数据的加密传输&#xff0c;有效保护用户隐私和数据安全。本文将详细介绍如何为IP地址申请SSL证书&#xff0c;并实现HTTPS加密访问。 一、准备工作 …

Kaggle入门-泰坦尼克号数据及代码

本文讲述了kaggle入门级别的竞赛&#xff1a;泰坦尼克号&#xff0c;有提及如何下载数据&#xff0c;附带有思路和代码解析 前言 我个人还是喜欢直接在kaggle运行&#xff0c;但是有人不能科学上网呀 数据 在找到泰坦尼克号比赛里&#xff0c;创建一个notebook&#xff0c;然…

Excel Module: Iteration #1 EasyExcel生成下拉列表模版时传入动态参数查询下拉数据

系列文章 EasyExcel生成带下拉列表或多级级联列表的Excel模版自定义校验导入数据(修订) 目录 系列文章前言仓库一、实现1.1 下拉元数据对象1.2 构建下拉元数据的映射关系1.3 框架方式1.3.1 框架实现1.3.2 框架用例模版类加载下拉业务导出接口 1.4 EasyExcel方式1.4.1 EasyExce…

数据仓库与数据挖掘实验练习3-4(实验二2024.5.8)

练习3 1.简单文件操作练习 import pandas as pd # 读取文件 pd.read_csv(pokemon.csv) # 读取 CSV 文件的函数调用&#xff0c;它将文件中的数据加载到 DataFrame 中&#xff0c;并指定了 Pokemon 列作为索引列。 pd.read_csv(pokemon.csv,index_colPokemon)#查看类型 type(p…

UE5材质基础(2)——数学节点篇1

UE5材质基础&#xff08;2&#xff09;——数学节点篇1 目录 UE5材质基础&#xff08;2&#xff09;——数学节点篇1 Add节点 Append节点 Abs节点 Subtract节点 Multiply节点 Divide节点 Clamp节点 Time节点 Lerp节点 Add节点 快捷键&#xff1a;A鼠标左键 值相加…

智慧安监中的物联网主机E6000

物联网主机E6000的研发背景主要源于我国对物联网技术在安全生产、环境监测、火灾预警与防控、人员定位与紧急救援等领域的迫切需求。近年来&#xff0c;随着物联网技术的飞速发展&#xff0c;我国政府对智慧安监的重视程度不断提升&#xff0c;相关的政策扶持力度也在加大。在这…

Ansible--Templates 模块 Tags模块 Roles模块

一 Templates 模块 ①Jinja是基于Python的模板引擎。Template类是Jinja的一个重要组件&#xff0c;可看作一个编译过的模 板文件&#xff0c;用来产生目标文本&#xff0c;传递Python的变量给模板去替换模板中的标记。 ②在配置文件中&#xff0c;会有一些数据&#xff08;如…

CCF-Csp算法能力认证, 202212-1现值计算(C++)含解析

前言 推荐书目&#xff0c;在这里推荐那一本《算法笔记》&#xff08;胡明&#xff09;&#xff0c;需要PDF的话&#xff0c;链接如下 「链接&#xff1a;https://pan.xunlei.com/s/VNvz4BUFYqnx8kJ4BI4v1ywPA1?pwd6vdq# 提取码&#xff1a;6vdq”复制这段内容后打开手机迅雷…

前端双语实现方案(VUE版)

一、封装一个lib包 结构如下 en.js use strict;exports.__esModule true; exports.default {sp: {input: {amountError: Incorrect amount format},table: {total: Total:,selected: Selected:,tableNoData: No data,tableNoDataSubtext: Tip: Suggest to recheck your fil…

LeetCode 110. 平衡二叉树

LeetCode 110. 平衡二叉树 1、题目 题目链接&#xff1a;110. 平衡二叉树 给定一个二叉树&#xff0c;判断它是否是 平衡二叉树 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2…

AI写作助手:推荐顶级论文写作工具

ChatGPT生成内容需要注意的问题 永远不要直接提交未经修改的AI文本使用工具如Quillbot、Versabot(支持中文论文生成和润色)、Paraphrasing Tool和Jasper来改变文本的措辞亲自修改短语、句子和文本的其他元素提示ChatGPT重新写自己的文本&#xff0c;并通过多个草稿进行修订 Ch…

如何把多个文件(夹)向上移动1层(或多层)(在批量复制前或后进行)

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 假定情况是&#xff0c;我要把下图里的4个文件夹内部的全部文件&#xff0c;合并到04的当前位置来&#xff08;4个文件夹里面各有5个兔兔的图片&#xff09…

【吃透Java手写】2-Spring(下)-AOP-事务及传播原理

【吃透Java手写】Spring&#xff08;下&#xff09;AOP-事务及传播原理 6 AOP模拟实现6.1 AOP工作流程6.2 定义dao接口与实现类6.3 初始化后逻辑6.4 原生Spring的方法6.4.1 实现类6.4.2 定义通知类&#xff0c;定义切入点表达式、配置切面6.4.3 在配置类中进行Spring注解包扫描…

华为ensp中BFD和OSPF联动(原理及配置命令)

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年5月6日20点26分 BFD通常指的是双向转发检测。BFD是一个旨在快速检测通信链路故障的网络协议&#xff0c;提供了低开销、短延迟的链路故障检测机制。它主要用于监测两个…

start.spring.io不支持java8,idea使用阿里云又报错

做项目的时候&#xff0c;我们可以发现&#xff0c;访问https://start.spring.io/ 创建脚手架项目的时候&#xff0c;最低是java 17了 但是对于很多项目来说&#xff0c;还是在用java8&#xff0c;这该怎么办呢&#xff1f; 值得庆幸的是&#xff0c;阿里云也同样有相同功能的…

VASP_AIMD+VASPKIT计算含温力学性质

材料的力学性质一直是DFT计算的重要方向&#xff0c;笔者在以往已有针对于静态结构的力学性质诸如弹性常数的相关计算&#xff0c;同时可通过VASPKIT借助相关方程导出力学性能。 bashvaspvaspkit能量应变计算弹性常数 vaspkit计算弹性常数的对称性指定 vaspkit计算弹性常数脚…

visual studio 2017重命名解决方案或项目名称

1.解决方案->右键->重命名->新的名字 2.项目->右键->重命名->新的名字 3.修改程序集和命名空间名称 项目->右键->属性->修改程序集名称和命名空间名称 4.搜索换名 Ctrl-F->输入旧名称->搜索->将所有旧名称改为新名称&#xff08;注意是整…

C++向函数传递对象

C语言中&#xff0c;对象作为函数的参数和返回值的传递方式有 3 种&#xff1a;值传递、指针传递和引用传递。 1. 对象作为函数参数 把实参对象的值复制给形参对象&#xff0c;这种传递是单向的&#xff0c;只从实参到形参。因此&#xff0c;函数对形参值做的改变不会影响到实…

使用Docker安装Whistle Web Debugging Proxy

大家好&#xff0c;继续给大家分享如何使用docker来安装Whistle Web Debugging Proxy&#xff0c;关于Whistle Web Debugging Proxy的介绍和使用&#xff0c;大家可以参考下面文章&#xff0c;希望本文能够给大家的工作带来一定帮助。 Whistle Web Debugging Proxy介绍及使用 …

《二十一》QT QML编程基础

QML概述 QML&#xff08;Qt Meta-Object Language&#xff09;是一种声明性语言&#xff0c;它被用于描述Qt框架中用户界面的结构和行为。QML提供了一种简洁、灵活的方式来创建动态和交互式的界面。 QML基于JavaScript语法&#xff0c;通过使用QML类型和属性来定义界面的元素…
最新文章