ALTER TABLE(列存删除列)
声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 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 DEFAULT
和 ALTER 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 函数用于根据给定的关系 id(relid)和属性名(attname)获取数据库表中对应列的属性编号(attnum)。具体实现步骤如下:
- 在系统缓存中搜索属性: 通过调用 SearchSysCacheAttName 函数,在系统缓存中搜索与给定关系 id 和属性名匹配的属性元组。
- 检查元组有效性: 如果检索到的元组有效,提取属性元组的结构体信息。
- 获取属性编号: 从属性元组中获取属性编号(attnum),并将其存储在 result 变量中。
- 释放系统缓存: 释放系统缓存中的元组,防止内存泄漏。
- 返回属性编号: 返回属性编号。如果属性不存在或已被删除,则返回 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 用于向现有关系(表)添加新的列默认表达式和/或检查约束表达式。其主要功能包括:
- 获取现有约束信息: 获取并保存关系中的现有约束信息。
- 创建解析状态: 创建解析状态,并将目标关系添加为其范围表条目。
- 处理列默认表达式: 遍历并处理新的列默认表达式,将它们转换为可执行的表达式,并存储在系统表中。如果表达式是 NULL 常量,则不生成显式的 pg_attrdef 条目。
- 处理检查约束表达式: 遍历并处理新的检查约束表达式,转换为可执行表达式,验证其有效性,并存储在系统表中。还确保检查约束名称的唯一性。
- 更新约束数量: 更新关系的 pg_class 元组中的约束数量,确保对其他后端的 relcache 条目进行更新。
- 锁定依赖的序列: 锁定约束所依赖的序列,确保数据一致性。
/*
* 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; // 返回处理后的约束列表
}