【OpenGauss源码学习 —— (ALTER COLUMN SET/DROP DEFAULT)】

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

列存 ALTER COLUMN SET/DROP DEFAULT

  在数据库管理中,ALTER TABLE 命令用于修改表的结构,其中 ALTER COLUMN 子句可以用来设置或删除列的默认值。对于列存储表,ALTER COLUMN SET DEFAULT 命令允许用户为指定列设置一个新的默认值,而 ALTER COLUMN DROP DEFAULT 命令则用于移除现有的默认值。这些操作有助于保持表数据的一致性,确保在插入新数据时自动应用正确的默认值。
  在 OpenGauss 中,ATExecColumnDefault 函数专门负责执行 ALTER COLUMN SET/DROP DEFAULT 操作。该函数首先验证列是否存在并且不是系统列或自动递增列,然后移除任何现有的默认值,并根据需要设置新的默认值。通过调用 ATExecColumnDefault 函数,数据库系统能够确保默认值的正确设置或移除,同时维护表的完整性和一致性。本文将围绕 ATExecColumnDefault 函数来展开学习。

ATExecColumnDefault 函数

  ATExecColumnDefault 函数在 OpenGauss 中用于执行 ALTER TABLE ALTER COLUMN SET DEFAULTALTER TABLE ALTER COLUMN DROP DEFAULT 命令。其作用是修改表中指定列的默认值。具体来说,该函数首先验证列是否存在且不是系统列生成列自动递增列,然后移除该列的现有默认值,并根据需要设置新的默认值。通过这一过程,ATExecColumnDefault 函数确保列的默认值被正确更新,维护数据的一致性和表的完整性,尤其是在列存储表中,确保高效的存储和数据准确性。

/*
 * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
 */
static ObjectAddress ATExecColumnDefault(Relation rel,
										 const char* colName,
										 Node* newDefault,
										 LOCKMODE lockmode)
{
    AttrNumber attnum; // 定义列的属性编号
    ObjectAddress address; // 定义对象地址
    TupleDesc tupdesc = RelationGetDescr(rel); // 获取表的元组描述信息

    /*
     * 获取列的属性编号
     */
    attnum = get_attnum(RelationGetRelid(rel), colName);
    if (attnum == InvalidAttrNumber) {
        // 如果列不存在,处理升级过程中的情况
        if (u_sess->attr.attr_common.IsInplaceUpgrade) {
            ereport(WARNING,
                (errcode(ERRCODE_UNDEFINED_COLUMN),
                    errmsg("column \"%s\" of relation \"%s\" does not exist while dropping default",
                        colName,
                        RelationGetRelationName(rel))));
            return InvalidObjectAddress;
        } else
            // 报告错误,列不存在
            ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_COLUMN),
                    errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
    }

    // 防止修改系统属性
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    // 检查是否为生成列
    if (ISGENERATEDCOL(tupdesc, attnum - 1)) {
        ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
            errmsg("column \"%s\" of relation \"%s\" is a generated column", colName, RelationGetRelationName(rel))));
    // 检查是否为自动递增列
    } else if (attnum == RelAutoIncAttrNum(rel)) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("cannot alter auto_increment column \"%s\" default", colName)));
    }

    // 获取是否存在on update表达式
    bool on_update = FetchOnUpdateExpress(rel, colName);

    /*
     * 删除列的任何旧的默认值。这里我们使用RESTRICT以确保安全,
     * 但目前我们不期望有任何依赖于默认值的内容。
     *
     * 如果是为了添加新默认值而删除现有默认值,我们将其视为内部操作,
     * 但如果用户要求删除,我们将其视为用户发起的操作。
     */
    if (!on_update) {
        RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, newDefault == NULL ? false : true);
    }

    // 如果有新的默认值,或者存在on update且没有新的默认值
    if (newDefault != NULL || (on_update && newDefault == NULL)) {
        /* 设置默认值 */
        RawColumnDefault* rawEnt = NULL;

        rawEnt = (RawColumnDefault*)palloc(sizeof(RawColumnDefault)); // 分配内存
        rawEnt->attnum = attnum; // 设置属性编号
        rawEnt->raw_default = newDefault; // 设置原始默认值
        rawEnt->generatedCol = '\0'; // 设置生成列标志
        rawEnt->update_expr = NULL; // 设置更新表达式

        /*
         * 该函数旨在用于CREATE TABLE,因此它处理默认值列表,但我们这里只处理一个。
         */
        (void)AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true);
    }

    // 设置对象地址
    ObjectAddressSubSet(address, RelationRelationId,
                        RelationGetRelid(rel), attnum);
    return address;
}

get_attnum 函数

  get_attnum 函数用于根据给定的关系 idrelid)和属性名attname)获取数据库表中对应列的属性编号(attnum。具体实现步骤如下:

  1. 在系统缓存中搜索属性: 通过调用 SearchSysCacheAttName 函数,在系统缓存中搜索与给定关系 id 和属性名匹配的属性元组。
  2. 检查元组有效性: 如果检索到的元组有效,提取属性元组的结构体信息
  3. 获取属性编号:属性元组中获取属性编号attnum),并将其存储在 result 变量中。
  4. 释放系统缓存: 释放系统缓存中的元组,防止内存泄漏。
  5. 返回属性编号: 返回属性编号。如果属性不存在或已被删除,则返回 InvalidAttrNumber

该函数的主要作用是通过查询系统缓存,快速获取指定表列的属性编号,用于后续的数据库操作中验证列的存在性和访问列的具体信息。函数源码如下所示:(路径:src\common\backend\utils\cache\lsyscache.cpp

/*
 * get_attnum
 *
 *		给定关系id和属性名,返回属性关系中的"attnum"字段。
 *
 *		如果属性不存在(或已被删除),则返回InvalidAttrNumber。
 */
AttrNumber get_attnum(Oid relid, const char* attname)
{
    HeapTuple tp; // 定义HeapTuple类型的变量,用于存储系统缓存中检索到的元组
    // 在系统缓存中搜索指定关系id和属性名的属性元组
    tp = SearchSysCacheAttName(relid, attname);
    // 如果检索到的元组有效
    if (HeapTupleIsValid(tp)) {
        // 获取属性元组的结构体
        Form_pg_attribute att_tup = (Form_pg_attribute)GETSTRUCT(tp);
        AttrNumber result; // 定义返回结果的变量
        result = att_tup->attnum; // 获取属性编号
        ReleaseSysCache(tp); // 释放系统缓存中的元组
        return result; // 返回属性编号
    } else {
        return InvalidAttrNumber; // 如果属性不存在,返回InvalidAttrNumber
    }
}

AddRelationNewConstraints 函数

  函数 AddRelationNewConstraints 用于向现有关系(表)添加新的列默认表达式和/或检查约束表达式。其主要功能包括:

  1. 获取现有约束信息: 获取并保存关系中的现有约束信息。
  2. 创建解析状态: 创建解析状态,并将目标关系添加为其范围表条目。
  3. 处理列默认表达式: 遍历并处理新的列默认表达式,将它们转换为可执行的表达式,并存储在系统表中。如果表达式是 NULL 常量,则不生成显式的 pg_attrdef 条目。
  4. 处理检查约束表达式: 遍历并处理新的检查约束表达式,转换为可执行表达式,验证其有效性,并存储在系统表中。还确保检查约束名称的唯一性。
  5. 更新约束数量: 更新关系的 pg_class 元组中的约束数量,确保对其他后端的 relcache 条目进行更新。
  6. 锁定依赖的序列: 锁定约束所依赖的序列,确保数据一致性。
/*
 * AddRelationNewConstraints
 *
 * 为现有关系添加新的列默认表达式和/或约束检查表达式。
 * 为了在DefineRelation中提高效率,这个函数被定义为同时处理两者,
 * 当然,您可以通过传递空列表来只处理其中一个。
 *
 * rel: 要修改的关系
 * newColDefaults: RawColumnDefault结构的列表
 * newConstraints: Constraint节点的列表
 * allow_merge: 如果检查约束可以与现有的约束合并,则为TRUE
 * is_local: 如果定义是本地的,则为TRUE;如果是继承的,则为FALSE
 *
 * newColDefaults中的所有条目都将被处理。newConstraints中的条目只有在它们是CONSTR_CHECK类型时才会被处理。
 *
 * 返回一个CookedConstraint节点的列表,显示添加到关系中的默认值和约束表达式的处理形式。
 *
 * 注意:调用者应以AccessExclusiveLock打开rel,并应保持该锁直到事务结束。
 * 此外,我们假设调用者已经执行了CommandCounterIncrement(如果需要)以使关系的目录元组可见。
 */
List* AddRelationNewConstraints(
    Relation rel, List* newColDefaults, List* newConstraints, bool allow_merge, bool is_local)
{
    List* cookedConstraints = NIL; // 存储处理后的约束列表
    TupleDesc tupleDesc; // 元组描述
    TupleConstr* oldconstr = NULL; // 旧约束
    int numoldchecks; // 旧检查约束的数量
    ParseState* pstate = NULL; // 解析状态
    RangeTblEntry* rte = NULL; // 范围表条目
    int numchecks; // 检查约束的数量
    List* checknames = NIL; // 检查约束名称列表
    ListCell* cell = NULL; // 列表单元
    Node* expr = NULL; // 表达式
    CookedConstraint* cooked = NULL; // 处理后的约束
    Oid defOid; // 默认值OID
    AttrNumber autoinc_attnum = RelAutoIncAttrNum(rel); // 自动递增列的属性编号
    Node* update_expr = NULL; // 更新表达式
    Bitmapset* generated_by_attrs = NULL; // 生成列的属性位图集合
    /*
     * 获取现有约束的信息。
     */
    tupleDesc = RelationGetDescr(rel); // 获取关系的元组描述
    oldconstr = tupleDesc->constr; // 获取旧的约束
    if (oldconstr != NULL)
        numoldchecks = oldconstr->num_check; // 获取旧检查约束的数量
    else
        numoldchecks = 0;

    /*
     * 创建一个虚拟的ParseState,并将目标关系作为其唯一的范围表条目插入。
     * 我们需要一个ParseState来转换表达式。
     */
    pstate = make_parsestate(NULL); // 创建解析状态
    rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true); // 添加范围表条目
    addRTEtoQuery(pstate, rte, true, true, true); // 将范围表条目添加到查询中

    pstate->p_rawdefaultlist = newColDefaults; // 设置解析状态中的原始默认值列表

    /*
     * 处理列默认表达式。
     */
    foreach (cell, newColDefaults) {
        RawColumnDefault* colDef = (RawColumnDefault*)lfirst(cell); // 获取列默认值定义
        Form_pg_attribute atp = &rel->rd_att->attrs[colDef->attnum - 1]; // 获取列属性

        if (colDef->raw_default != NULL) {
            if (IsA(colDef->raw_default, AutoIncrement)) {
                autoinc_attnum = colDef->attnum; // 设置自动递增列的属性编号
                expr = CookAutoIncDefault(pstate, rel, colDef, atp); // 处理自动递增默认值
            } else {
                expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod,
                    atp->attcollation, NameStr(atp->attname), colDef->generatedCol); // 处理默认值
                if (colDef->generatedCol == ATTRIBUTE_GENERATED_STORED) {
                    pull_varattnos(expr, 1, &generated_by_attrs); // 提取生成列的属性编号
                }
            }
        }

        if (colDef->update_expr != NULL) {
            update_expr = cookDefault(pstate, colDef->update_expr, atp->atttypid, atp->atttypmod,
                atp->attcollation, NameStr(atp->attname), colDef->generatedCol); // 处理更新表达式
        }

        /*
         * 如果表达式只是一个NULL常量,我们不会生成显式的pg_attrdef条目,因为默认行为是等效的。
         * 这适用于列默认值,但不适用于生成表达式。
         *
         * 注意这个测试的一个不明显的属性:如果列是域类型,我们得到的不是一个裸的null Const,而是一个CoerceToDomain表达式,
         * 所以我们不会丢弃默认值。这是至关重要的,因为需要保留列默认值以覆盖域可能具有的任何默认值。
         */
        if (expr != NULL && !colDef->generatedCol && IsA(expr, Const) && ((Const *)expr)->constisnull)
            continue;

        defOid = StoreAttrDefault(rel, colDef->attnum, expr, colDef->generatedCol, update_expr); // 存储列默认值

        cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint)); // 分配内存
        cooked->contype = CONSTR_DEFAULT; // 设置约束类型为默认值
        cooked->conoid = defOid; // 设置约束OID
        cooked->name = NULL; // 约束名称为空
        cooked->attnum = colDef->attnum; // 设置属性编号
        cooked->expr = expr; // 设置表达式
        cooked->skip_validation = false; // 不跳过验证
        cooked->is_local = is_local; // 设置是否为本地定义
        cooked->inhcount = is_local ? 0 : 1; // 设置继承计数
        cooked->is_no_inherit = false; // 设置是否禁止继承
        cookedConstraints = lappend(cookedConstraints, cooked); // 将处理后的约束添加到列表中
        expr = NULL; // 重置表达式
        update_expr = NULL; // 重置更新表达式
    }

    if (autoinc_attnum > 0 &&
        bms_is_member(autoinc_attnum - FirstLowInvalidHeapAttributeNumber, generated_by_attrs)) {
        bms_free_ext(generated_by_attrs); // 释放生成列的属性位图集合
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            (errmsg("generated column cannot refer to auto_increment column")))); // 报告错误,生成列不能引用自动递增列
    }

    bms_free_ext(generated_by_attrs); // 释放生成列的属性位图集合

    pstate->p_rawdefaultlist = NIL; // 清空解析状态中的原始默认值列表

    /*
     * 处理约束表达式。
     */
    numchecks = numoldchecks; // 初始化检查约束数量
    checknames = NIL; // 初始化检查约束名称列表
    foreach (cell, newConstraints) {
        Constraint* cdef = (Constraint*)lfirst(cell); // 获取约束定义
        char* ccname = NULL; // 初始化检查约束名称
        Oid constrOid;

        if (cdef->contype != CONSTR_CHECK)
            continue;

        if (cdef->raw_expr != NULL) {
            Assert(cdef->cooked_expr == NULL);

            /*
             * 将原始解析树转换为可执行表达式,并验证它作为CHECK约束的有效性。
             */
            expr = cookConstraint(pstate, cdef->raw_expr, RelationGetRelationName(rel));
        } else {
            Assert(cdef->cooked_expr != NULL);

            /*
             * 在这里,我们假设解析器只会传递有效的CHECK表达式,所以我们不进行特别的检查。
             */
            expr = (Node*)stringToNode(cdef->cooked_expr);
        }

        /*
         * 检查约束表达式不能包含自动递增列。
         */
        CheckAutoIncrementCheckExpr(expr, autoinc_attnum);

        /*
         * 检查名称的唯一性,如果没有给出名称,则生成一个名称。
         */
        if (cdef->conname != NULL) {
            ListCell* cell2 = NULL;

            ccname = cdef->conname;
            /* 检查其他新约束 */
            /* 需要,因为我们在循环中不执行CommandCounterIncrement */
            foreach (cell2, checknames) {
                if (strcmp((char*)lfirst(cell2), ccname) == 0)
                    ereport(ERROR,
                        (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("check constraint \"%s\" already exists", ccname)));
            }

            /* 保存名称以供将来检查 */
            checknames = lappend(checknames, ccname);

            /*
             * 检查预先存在的约束。如果允许我们与现有约束合并,这里不需要做更多。
             * (我们省略了重复的约束,这正是ATAddCheckConstraint想要的。)
             */
            if (MergeWithExistingConstraint(rel, ccname, expr, allow_merge, is_local, cdef->is_no_inherit))
                continue;
        } else {
            /*
             * 生成名称时,我们希望为列约束创建"tab_col_check",为表约束创建"tab_check"。
             * 我们不再拥有关于约束短语语法位置的任何信息,所以我们通过查看表达式是否引用多个列来近似这个结果。
             * (如果用户遵守规则,结果是相同的...)
             *
             * 注意:pull_var_clause()不会深入到子链接中,但我们在上面已经消除了这些;
             * 而且无论如何这只是一个近似答案。
             */
            List* vars = NIL;
            char* colname = NULL;

            vars = pull_var_clause(expr, PVC_REJECT_AGGREGATES, PVC_REJECT_PLACEHOLDERS);

            /* 消除重复项 */
            vars = list_union(NIL, vars);

            if (list_length(vars) == 1)
                colname = get_attname(RelationGetRelid(rel), ((Var*)linitial(vars))->varattno);
            else
                colname = NULL;

            ccname = ChooseConstraintName(
                RelationGetRelationName(rel), colname, "check", RelationGetNamespace(rel), checknames);

            /* 保存名称以供将来检查 */
            checknames = lappend(checknames, ccname);
        }

        /*
         * 存储约束。
         */
        constrOid = StoreRelCheck(rel, ccname, expr, !cdef->skip_validation, 
                                    is_local, is_local ? 0 : 1, cdef->is_no_inherit);

        ListCell *cell = NULL;
        foreach (cell, cdef->constraintOptions) {
            void *pointer = lfirst(cell);
            if (IsA(pointer, CommentStmt)) {
                CommentStmt *commentStmt = (CommentStmt *)pointer;
                CreateComments(constrOid, ConstraintRelationId, 0, commentStmt->comment);
                break;
            }
        }

        numchecks++; // 增加检查约束数量

        cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint)); // 分配内存
        cooked->contype = CONSTR_CHECK; // 设置约束类型为检查约束
        cooked->conoid = constrOid; // 设置约束OID
        cooked->name = ccname; // 设置约束名称
        cooked->attnum = 0; // 设置属性编号为0
        cooked->expr = expr; // 设置表达式
        cooked->skip_validation = cdef->skip_validation; // 设置是否跳过验证
        cooked->is_local = is_local; // 设置是否为本地定义
        cooked->inhcount = is_local ? 0 : 1; // 设置继承计数
        cooked->is_no_inherit = cdef->is_no_inherit; // 设置是否禁止继承
        cookedConstraints = lappend(cookedConstraints, cooked); // 将处理后的约束添加到列表中
    }

    /*
     * 更新关系的pg_class元组中的约束数量。
     * 即使没有变化,我们也这样做,以确保为pg_class元组发送一个SI更新消息,
     * 这将强制其他后端重新构建它们的relcache条目。(如果我们添加了默认值但没有约束,这一点至关重要。)
     */
    SetRelationNumChecks(rel, numchecks);

    /*
     * 锁定约束所依赖的序列,我们可能在更远的地方锁定其他对象。
     */
    LockSeqConstraints(rel, cookedConstraints);

    return cookedConstraints; // 返回处理后的约束列表
}
  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值