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

ALTER TABLE(SET attribute_option)

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

  ALTER COLUMN ... SET attribute_option 是一种 SQL 命令,用于修改数据库表中特定列的属性选项。在 OpenGauss 中,这种命令可以用来改变列的默认值、是否允许为空、数据类型等属性,以满足不同的业务需求或数据结构变更。
  在 OpenGauss 的源代码中,处理 ALTER COLUMN SETALTER COLUMN RESET 的逻辑通常涉及到调用 ATExecSetOptions 函数。例如,当 SQL 命令中包含 SET ( options ) 时,会执行 ATExecSetOptions 函数来实际应用这些选项,而当包含 RESET ( options ) 时,则会反向执行这些选项的重置操作
  ATExecSetOptions 函数的作用是根据传入的参数,对指定表的特定列应用或重置属性选项。这包括解析和验证命令中的选项,然后更新表的元数据,确保数据库表的结构变更符合预期并且安全。这个函数不仅仅负责修改列的默认值,还可能涉及到其他属性的调整,如约束条件或索引的更新
  总之,通过 ALTER COLUMN ... SET attribute_option 命令和相关的函数调用,OpenGauss 提供了强大的能力来动态调整和管理数据库表的结构,从而支持复杂的应用和业务需求变化。

ATExecSetOptions 函数

  ATExecSetOptions 函数是实现 ALTER COLUMN ... SET/RESET attribute_option 功能的核心逻辑。它通过修改指定表中特定列的属性选项,包括默认值是否允许为空等,具体步骤包括打开系统表获取目标列的元数据验证和更新选项值,最后将修改应用到系统目录并确保索引更新,从而保证数据库表结构的变更和一致性。其执行流程如下:

  1. 打开 pg_attribute 系统表,获取对应关系的独占锁。
  2. 在系统缓存中搜索并获取指定列名的元组
  3. 如果未找到有效的列元组,报错指示指定的列在目标表中不存在
  4. 从列元组中获取列的属性信息,包括列号attnum)等。
  5. 验证传入的选项参数确保其为列表(List)类型,并禁止为系统列设置选项。
  6. 根据传入的选项,使用 transformRelOptions 函数生成新的属性选项attoptions)的文本数组。
  7. 调用 attribute_reloptions 函数验证新生成的选项是否合法
  8. 准备一个新的替换数组,根据新选项是否为 NULL 设置相应的值。
  9. 使用 tableam_tops_modify_tuple 函数构建新的 HeapTuple 对象,用于更新系统表中的列元组。
  10. 释放之前获取的原始列元组缓存
  11. 使用 simple_heap_update 函数将更新后的新元组写入系统表中,并更新系统表的索引
  12. 设置返回的对象地址,指示操作的对象为目标表中的指定列
  13. 释放不再需要的 HeapTuple 对象。
  14. 关闭 pg_attribute 系统表,释放其占用的独占锁。
  15. 返回最终的对象地址,表示 ALTER COLUMN ... SET 操作的成功完成。

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

static ObjectAddress ATExecSetOptions(Relation rel, const char* colName, Node* options, bool isReset, LOCKMODE lockmode)
{
    // 打开属性关系的堆表
    Relation attrelation;
    // 原始元组、新元组和属性元组
    HeapTuple tuple, newtuple;
    Form_pg_attribute attrtuple;
    // 列号
    AttrNumber  attnum;
    // 数据、新选项
    Datum datum, newOptions;
    bool isnull = false;
    // 替换值数组
    Datum repl_val[Natts_pg_attribute];
    // 替换空值标志数组
    bool repl_null[Natts_pg_attribute];
    // 替换标志数组
    bool repl_repl[Natts_pg_attribute];
    // 返回对象地址
    ObjectAddress address;

    // 打开属性关系表
    attrelation = heap_open(AttributeRelationId, RowExclusiveLock);

    // 根据列名从系统缓存中查找元组
    tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);

    // 如果未找到有效元组,报错
    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
    // 获取属性元组结构
    attrtuple = (Form_pg_attribute)GETSTRUCT(tuple);

    // 获取列号
    attnum = attrtuple->attnum;
    // 如果列号小于等于0,报错(不支持修改系统列)
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    // 断言选项为列表类型
    Assert(IsA(options, List));
    // 禁止为属性设置选项
    ForbidToSetOptionsForAttribute((List*)options);

    // 生成新的建议 attoptions(文本数组)
    datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, &isnull);
	newOptions = transformRelOptions(isnull ? (Datum)0 : datum, (List*)options, NULL, NULL, false, isReset);
	    // 使用 transformRelOptions 转换属性选项,根据 isnull 和 isReset 参数处理旧选项和新选项
	
	// 验证新选项
	(void)attribute_reloptions(newOptions, true);
	
	// 初始化替换标记数组为 false
	rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
	securec_check(rc, "\0", "\0");
	rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
	securec_check(rc, "\0", "\0");
	
	// 如果新选项不为空,则设置替换的值为新选项,否则设置替换标记为 true 表示空值
	if (newOptions != (Datum)0)
	    repl_val[Anum_pg_attribute_attoptions - 1] = newOptions;
	else
	    repl_null[Anum_pg_attribute_attoptions - 1] = true;
	repl_repl[Anum_pg_attribute_attoptions - 1] = true;
	
	// 通过 tableam_tops_modify_tuple 创建一个新的 HeapTuple,用于更新系统表中的属性选项
	newtuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(attrelation), repl_val, repl_null, repl_repl);
	ReleaseSysCache(tuple); // 释放旧的属性元组缓存
	
	// 使用 simple_heap_update 更新系统表中的属性元组
	simple_heap_update(attrelation, &newtuple->t_self, newtuple);
	CatalogUpdateIndexes(attrelation, newtuple); // 更新系统目录索引
	
	// 设置返回对象地址信息
	ObjectAddressSubSet(address, RelationRelationId,
	                    RelationGetRelid(rel), attnum);
	
	// 释放新的属性元组内存
	tableam_tops_free_tuple(newtuple);
	
	// 关闭属性关系表
	heap_close(attrelation, RowExclusiveLock);
	
	return address; // 返回更新后的对象地址
	
}

  ATExecSetOptions 函数主要针对表的列进行选项设置的变更。对于 RESET 操作,它会重置指定列的选项设置为空,并更新系统表中的元组数据,确保数据库结构的一致性和正确性。

transformRelOptions 函数

  transformRelOptions 函数实现了将关系选项列表(DefElem 列表)转换为数据库中 pg_class.reloptions 表中保存的文本数组格式。它支持在指定命名空间中包括并处理选项,根据需要替换或移除现有的 reloptions 条目。在处理过程中,还支持对命名空间的有效性检查,确保所有给定选项的命名空间在有效命名空间列表中。函数源码如下所示:(路径:src\gausskernel\storage\access\common\reloptions.cpp

/*
 * Transform a relation options list (list of DefElem) into the text array
 * format that is kept in pg_class.reloptions, including only those options
 * that are in the passed namespace.  The output values do not include the
 * namespace.
 *
 * 将关系选项列表(DefElem 列表)转换为文本数组格式,该格式保存在 pg_class.reloptions 中,
 * 只包括位于指定命名空间中的选项。输出的值不包括命名空间本身。
 *
 * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and
 * ALTER TABLE RESET.  In the ALTER cases, oldOptions is the existing
 * reloptions value (possibly NULL), and we replace or remove entries
 * as needed.
 *
 * 该函数用于三种情况:CREATE TABLE/INDEX、ALTER TABLE SET 和 ALTER TABLE RESET。
 * 在 ALTER 操作中,oldOptions 是现有的 reloptions 值(可能为 NULL),我们根据需要替换或删除条目。
 *
 * If ignoreOids is true, then we should ignore any occurrence of "oids"
 * in the list (it will be or has been handled by interpretOidsOption()).
 *
 * 如果 ignoreOids 为 true,则应忽略列表中的任何 "oids" 出现(这将由 interpretOidsOption() 处理)。
 *
 * Note that this is not responsible for determining whether the options
 * are valid, but it does check that namespaces for all the options given are
 * listed in validnsps.  The NULL namespace is always valid and need not be
 * explicitly listed.  Passing a NULL pointer means that only the NULL
 * namespace is valid.
 *
 * 注意,此函数不负责确定选项是否有效,但它会检查所有给定选项的命名空间是否列在 validnsps 中。
 * NULL 命名空间始终有效,无需显式列出。传递 NULL 指针意味着只有 NULL 命名空间是有效的。
 */
Datum transformRelOptions(Datum oldOptions, List *defList, const char *namspace, const char *const validnsps[],
                          bool ignoreOids, bool isReset)
{
    Datum result;
    ArrayBuildState *astate = NULL;
    ListCell *cell = NULL;

    /* no change if empty list */
    // 如果选项列表为空,则无需更改
    if (defList == NIL)
        return oldOptions;

    /* We build new array using accumArrayResult */
    // 使用 accumArrayResult 构建新数组
    astate = NULL;

    /* Copy any oldOptions that aren't to be replaced */
    // 复制不需要替换的任何旧选项
    if (PointerIsValid(DatumGetPointer(oldOptions))) {
        ArrayType *array = DatumGetArrayTypeP(oldOptions);
        Datum *oldoptions = NULL;
        int noldoptions;
        int i;

        Assert(ARR_ELEMTYPE(array) == TEXTOID);

        deconstruct_array(array, TEXTOID, -1, false, 'i', &oldoptions, NULL, &noldoptions);

        for (i = 0; i < noldoptions; i++) {
            text *oldoption = DatumGetTextP(oldoptions[i]);
            char *text_str = VARDATA(oldoption);
            int text_len = VARSIZE(oldoption) - VARHDRSZ;

            /* Search for a match in defList */
            // 在 defList 中查找匹配项
            foreach (cell, defList) {
                DefElem *def = (DefElem *)lfirst(cell);
                int kw_len;
                
                /* ignore if not in the same namespace */
                // 如果不在相同的命名空间,则忽略
                if (namspace == NULL) {
                    if (def->defnamespace != NULL)
                        continue;
                } else if (def->defnamespace == NULL)
                    continue;
                else if (pg_strcasecmp(def->defnamespace, namspace) != 0)
                    continue;

                kw_len = strlen(def->defname);
                // 检查是否匹配选项名和命名空间
                if (text_len > kw_len && text_str[kw_len] == '=' && pg_strncasecmp(text_str, def->defname, kw_len) == 0)
                    break;
            }
            if (cell == NULL) {
                /* No match, so keep old option */
                // 没有匹配项,保留旧选项
                astate = accumArrayResult(astate, oldoptions[i], false, TEXTOID, CurrentMemoryContext);
            }
        }

        /* Free the memory used by array. */
        // 释放数组使用的内存
        if (DatumGetPointer(oldOptions) != DatumGetPointer(array)) {
            pfree(array);
        }
        pfree(oldoptions);
    }

    /*
     * If CREATE/SET, add new options to array; if RESET, just check that the
     * user didn't say RESET (option=val).  (Must do this because the grammar
     * doesn't enforce it.)
     */
    // 如果是 CREATE/SET,则将新选项添加到数组中;如果是 RESET,则仅检查用户是否未指定 RESET (option=val)。
    // 这必须这样做是因为语法不会强制执行此操作。
    const char *storageType = NULL;
    bool toastStorageTypeSet = false;
    foreach (cell, defList) {
        DefElem *def = (DefElem *)lfirst(cell);

        if (isReset) {
            if (def->arg != NULL)
                ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("RESET must not include values for parameters")));
        } else {
            text *t = NULL;
            const char *value = NULL;
            Size len;
            errno_t rc = EOK;

            /*
             * Error out if the namespace is not valid.  A NULL namespace is
             * always valid.
             */
            // 如果命名空间无效,则报错。NULL 命名空间始终有效。
            if (def->defnamespace != NULL) {
                bool valid = false;
                int i;

                if (validnsps) {
                    for (i = 0; validnsps[i]; i++) {
                        if (pg_strcasecmp(def->defnamespace, validnsps[i]) == 0) {
                            valid = true;
                            break;
                        }
                    }
                }

                if (!valid)
                    ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                    errmsg("unrecognized parameter namespace \"%s\"", def->defnamespace)));
            }

            if (ignoreOids && pg_strcasecmp(def->defname, "oids") == 0)
                continue;

            /* ignore if not in the same namespace */
            // 如果不在相同的命名空间,则忽略
            if (namspace == NULL) {
                if (def->defnamespace != NULL)
                    continue;
            } else if (pg_strcasecmp(def->defname, "storage_type") == 0 && pg_strcasecmp(namspace, "toast") == 0) {
                /* save the storage type of parent table for toast table, may be used as its storage type */
                // 为 toast 表保存父表的存储类型,可能用作其存储类型
                if (def->defnamespace == NULL) {
                    /* save parent storage type for toast */
                    // 保存 toast 表的父存储类型
                    storageType = ((def->arg != NULL) ? defGetString(def) : "true");
                    continue;
                } else if (pg_strcasecmp(def->defnamespace, namspace) != 0) {
                    continue;
                }
                toastStorageTypeSet = true; /* toast table set the storage type itself */
            } else if (def->defnamespace == NULL)
                continue;
            else if (pg_strcasecmp(def->defnamespace, namspace) != 0)
                continue;

            /*
             * Flatten the DefElem into a text string like "name=arg". If we
             * have just "name", assume "name=true" is meant.  Note: the
             * namespace is not output.
             */
            // 将 DefElem 展开为类似于 "name=arg" 的文本字符串。如果只有 "name",则假定为 "name=true"。
            // 注意:不输出命名空间。
            if (def->arg != NULL)
                value = defGetString(def);
            else
                value = "true";

            len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value);
            /* +1 leaves room for sprintf's trailing null */
            // +1 留出 sprintf 的结尾空间
            t = (text *)palloc(len + 1);
            SET_VARSIZE(t, len);
            rc = sprintf_s(VARDATA(t), len + 1, "%s=%s", def->defname, value);
            securec_check_ss(rc, "\0", "\0");
            astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext);
        }
    }

    /* we did not specify a storage type for toast, so use the same storage type as its parent */
    // 对于 toast 表未指定存储类型,则使用其父表的存储类型
    if (namspace != NULL && pg_strcasecmp(namspace, "toast") == 0 && !toastStorageTypeSet) {
        if (storageType != NULL) {
            Size len = VARHDRSZ + strlen("storage_type") + 1 + strlen(storageType);
            /* +1 leaves room for sprintf's trailing null */
            // +1 留出 sprintf 的结尾空间
            text *t = (text *)palloc(len + 1);
            SET_VARSIZE(t, len);
            errno_t rc = sprintf_s(VARDATA(t), len + 1, "%s=%s", "storage_type", storageType);
            securec_check_ss(rc, "\0", "\0");
            astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext);
        }
    }

    if (astate != NULL)
        result = makeArrayResult(astate, CurrentMemoryContext);
    else
        result = (Datum)0;

    return result;
}
  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值