暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

海山数据库(He3DB)源码解读:T_AlterRoleSetStmt原理浅析

cxp 2024-12-14
50

一、概述

   AlterRoleSet 在 He3DB 中允许你为特定角色设置默认的会话参数。这个功能能够使角色在连接数据库时自动应用某些参数配置。

二、AlterRoleSet 命令的执行流程

  1. PostgresMain
  2. exec_simple_query →执行简单的 SQL 查询;
  3. StartTransactionCommand → 开始事务;
  4. pg_parse_query →解析为内部的抽象语法树(AST);
  5. PortalRun
  6. standard_ProcessUtility →权限检查和准备;
  7. AlterRoleSet→设置默认的会话参数;
  8. CommandCounterIncrement→增量更新当前的命令计数器;
  9. CommitTransactionCommand→ 提交当前事务;
  10. finish_xact_command→ 在事务结束时,执行必要的清理和关闭操作;
    在这里插入图片描述
图1 AlterRoleSet 命令的执行流程图

三、核心结构体介绍

 (一)  AlterRoleSetStmt 是一个用于表示“ALTER ROLE … SET”语句的语法树节点的结构体。这种结构体通常在 He3DB 的解析和执行过程中使用,以处理 ALTER ROLE 命令。

typedef struct AlterRoleSetStmt { NodeTag type; RoleSpec *role; /* role */ char *database; /* database name, or NULL */ VariableSetStmt *setstmt; /* SET or RESET subcommand */ } AlterRoleSetStmt;

NodeTag type:

用于标识节点的类型。这是一个通用字段,能帮助 PostgreSQL 识别语法树中的各种节点类型。
*RoleSpec role:

指向一个 RoleSpec 结构,该结构包含了要修改的角色的信息。RoleSpec 通常包含角色名和角色的其他细节(如是否是一个用户或组角色)。
*char database:

该字段表示数据库的名称,通常在使用 ALTER ROLE … SET 时可以指定。如果为 NULL,则表示这条命令对所有数据库都适用。
*VariableSetStmt setstmt:

指向一个 VariableSetStmt 结构,该结构描述要为角色设置或重置的参数。它包含了具体的参数名和要设置的值,或者是一个表示重置的指令。

四、核心代码解析

   在 He3DB 的源代码中, AlterRoleSet(@src\backend\commands\user.c) 函数是指在数据库管理系统中用来修改角色的相关设置或权限的函数。具体的实现和作用依赖于所使用的数据库管理系统。

Oid AlterRoleSet(AlterRoleSetStmt *stmt)

Oid: 返回值类型,表示一个对象标识符(OID)。
AlterRoleSet(AlterRoleSetStmt *stmt): 函数名和参数,接收一个指向 AlterRoleSetStmt 结构体的指针,表示要执行的 ALTER ROLE 操作。

HeapTuple roletuple; Form_pg_authid roleform; Oid databaseid = InvalidOid; Oid roleid = InvalidOid;

HeapTuple roletuple: 存储角色元组的信息。
Form_pg_authid roleform: 用于访问角色的详细信息。
Oid databaseid = InvalidOid: 初始化数据库标识符,表示无效的 OID。
Oid roleid = InvalidOid: 初始化角色标识符,表示无效的 OID。

if (stmt->role) { check_rolespec_name(stmt->role, _("Cannot alter reserved roles."));

if (stmt->role): 如果指定了要修改的角色。
check_rolespec_name(…): 检查角色名称,确保不能更改预留角色(如 postgres 或其他系统角色)。

roletuple = get_rolespec_tuple(stmt->role); roleform = (Form_pg_authid) GETSTRUCT(roletuple); roleid = roleform->oid;

get_rolespec_tuple(stmt->role): 从角色规范中获取对应的角色元组。
roleform = (Form_pg_authid) GETSTRUCT(roletuple): 将元组转换为 pg_authid 结构形式,便于访问字段。
roleid = roleform->oid: 获取角色的 OID。

/* * Obtain a lock on the role and make sure it didn't go away in the * meantime. */ shdepLockAndCheckObject(AuthIdRelationId, roleid);

shdepLockAndCheckObject(…): 为角色获取锁以防止并发修改,同时检查该角色是否存在。

/* * To mess with a superuser you gotta be superuser; else you need * createrole, or just want to change your own settings */ if (roleform->rolsuper) { if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to alter superusers"))); }

roleform->rolsuper: 检查角色是否是超级用户。
if (!superuser()): 如果不是超级用户,返回错误,提示需要超级用户权限才能修改超级用户。

else { if (!have_createrole_privilege() && roleid != GetUserId()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied"))); }

!have_createrole_privilege(): 检查当前用户是否有创建角色的权限。
roleid != GetUserId(): 如果试图修改的角色不是当前用户。
ereport(ERROR, …): 记录错误信息,说明权限不足。

ReleaseSysCache(roletuple); }

ReleaseSysCache(roletuple): 释放角色元组的系统缓存,回收资源。

if (stmt->database != NULL) { databaseid = get_database_oid(stmt->database, false); shdepLockAndCheckObject(DatabaseRelationId, databaseid);

if (stmt->database != NULL): 如果指定了数据库。
get_database_oid(stmt->database, false): 获取指定数据库的 OID。
shdepLockAndCheckObject(…): 获取数据库的锁,并检查其存在性。

if (!stmt->role) { /* * If no role is specified, then this is effectively the same as * ALTER DATABASE ... SET, so use the same permission check. */ if (!pg_database_ownercheck(databaseid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE, stmt->database); } }

if (!stmt->role): 如果没有指定角色,表示这是 ALTER DATABASE 操作。
pg_database_ownercheck(…): 检查当前用户是否为数据库的所有者。
aclcheck_error(…): 如果不是所有者,返回权限检查错误。

if (!stmt->role && !stmt->database) { /* Must be superuser to alter settings globally. */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to alter settings globally"))); }

if (!stmt->role && !stmt->database): 如果既没有指定角色也没有指定数据库。
ereport(ERROR, …): 返回错误,说明必须是超级用户才能全局修改设置。

AlterSetting(databaseid, roleid, stmt->setstmt);

AlterSetting(databaseid, roleid, stmt->setstmt): 实际调用该函数更改角色或数据库的设置。

return roleid;

return roleid: 返回被修改角色的 OID。

AlterSetting用于修改或重置数据库角色的设置。它通过操作系统表 pg_db_role_setting 来实现这一功能。

void AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) { char *valuestr = NULL; HeapTuple tuple; Relation rel; ScanKeyData scankey[2] = {0}; SysScanDesc scan;

AlterSetting: 函数名,接受三个参数:databaseid(数据库OID)、roleid(角色OID)和 setstmt(设置语句结构体)。
valuestr: 用于存储从 setstmt 中提取的设置值。
tuple: 用于存储从系统表中获取的元组(行)。
rel: 表示系统表的结构体。
scankey: 用于扫描系统表的键值。
scan: 扫描系统表的描述符。

valuestr = ExtractSetVariableArgs(setstmt);

ExtractSetVariableArgs: 函数,从 setstmt 中提取设置的参数,并将其存储在 valuestr 中。

rel = table_open(DbRoleSettingRelationId, RowExclusiveLock); ScanKeyInit(&scankey[0], Anum_pg_db_role_setting_setdatabase, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(databaseid)); ScanKeyInit(&scankey[1], Anum_pg_db_role_setting_setrole, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(roleid)); scan = systable_beginscan(rel, DbRoleSettingDatidRolidIndexId, true, NULL, 2, scankey);

table_open: 打开系统表 pg_db_role_setting,并获取 RowExclusiveLock 锁。
ScanKeyInit: 初始化扫描键,用于匹配数据库OID和角色OID。
systable_beginscan: 开始扫描系统表,使用索引 DbRoleSettingDatidRolidIndexId 进行扫描。

tuple = systable_getnext(scan);

systable_getnext: 获取扫描到的下一个元组。

if (setstmt->kind == VAR_RESET_ALL) { // 处理 RESET ALL 命令 } else if (HeapTupleIsValid(tuple)) { // 处理其他命令(更新已有元组) } else if (valuestr) { // 处理其他命令(插入新元组) }

VAR_RESET_ALL: 如果设置语句的类型是 RESET ALL,表示要重置所有设置。
HeapTupleIsValid: 检查元组是否有效,即是否存在匹配的行。
valuestr: 检查是否存在设置值(非 RESET 命令)。

if (HeapTupleIsValid(tuple)) { ArrayType *new = NULL; Datum datum; bool isnull = false; datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig, RelationGetDescr(rel), &isnull); if (!isnull) new = GUCArrayReset(DatumGetArrayTypeP(datum)); if (new) { Datum repl_val[Natts_pg_db_role_setting] = {0}; bool repl_null[Natts_pg_db_role_setting] = {0}; bool repl_repl[Natts_pg_db_role_setting] = {0}; HeapTuple newtuple; { errno_t retc = 0; if (sizeof(repl_repl) > 0) { retc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl)); } securec_check_c(retc, "\0", "\0"); } repl_val[Anum_pg_db_role_setting_setconfig - 1] = PointerGetDatum(new); repl_repl[Anum_pg_db_role_setting_setconfig - 1] = true; repl_null[Anum_pg_db_role_setting_setconfig - 1] = false; newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl); CatalogTupleUpdate(rel, &tuple->t_self, newtuple); } else CatalogTupleDelete(rel, &tuple->t_self); }

heap_getattr: 获取元组中 setconfig 属性的值。
GUCArrayReset: 重置设置数组。
heap_modify_tuple: 创建并更新新的元组。
CatalogTupleUpdate: 更新系统表中的元组。
CatalogTupleDelete: 如果重置后数组为空,则删除该元组。

else if (HeapTupleIsValid(tuple)) { Datum repl_val[Natts_pg_db_role_setting] = {0}; bool repl_null[Natts_pg_db_role_setting] = {0}; bool repl_repl[Natts_pg_db_role_setting] = {0}; HeapTuple newtuple; Datum datum; bool isnull = false; ArrayType *a = NULL; { errno_t retc = 0; if (sizeof(repl_repl) > 0) { retc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl)); } securec_check_c(retc, "\0", "\0"); } repl_repl[Anum_pg_db_role_setting_setconfig - 1] = true; repl_null[Anum_pg_db_role_setting_setconfig - 1] = false; /* Extract old value of setconfig */ datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig, RelationGetDescr(rel), &isnull); a = isnull ? NULL : DatumGetArrayTypeP(datum); /* Update (valuestr is NULL in RESET cases) */ if (valuestr) a = GUCArrayAdd(a, setstmt->name, valuestr); else a = GUCArrayDelete(a, setstmt->name); if (a) { repl_val[Anum_pg_db_role_setting_setconfig - 1] = PointerGetDatum(a); newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl); CatalogTupleUpdate(rel, &tuple->t_self, newtuple); } else CatalogTupleDelete(rel, &tuple->t_self); }

heap_getattr: 获取元组中 setconfig 属性的值。
GUCArrayAdd: 添加新的设置值到数组。
GUCArrayDelete: 从数组中删除指定的设置值。
heap_modify_tuple: 创建并更新新的元组。
CatalogTupleUpdate: 更新系统表中的元组。
CatalogTupleDelete: 如果更新后数组为空,则删除该元组。

else if (valuestr) { HeapTuple newtuple; Datum values[Natts_pg_db_role_setting] = {0}; bool nulls[Natts_pg_db_role_setting] = {0}; ArrayType *a = NULL; { errno_t retc = 0; if (sizeof(nulls) > 0) { retc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls)); } securec_check_c(retc, "\0", "\0"); } a = GUCArrayAdd(NULL, setstmt->name, valuestr); values[Anum_pg_db_role_setting_setdatabase - 1] = ObjectIdGetDatum(databaseid); values[Anum_pg_db_role_setting_setrole - 1] = ObjectIdGetDatum(roleid); values[Anum_pg_db_role_setting_setconfig - 1] = PointerGetDatum(a); newtuple = heap_form_tuple(RelationGetDescr(rel), values, nulls); CatalogTupleInsert(rel, newtuple); }

GUCArrayAdd: 创建一个新的数组,并添加设置值。
heap_form_tuple: 创建一个新的元组。
CatalogTupleInsert: 将新元组插入系统表中。

InvokeObjectPostAlterHookArg(DbRoleSettingRelationId, databaseid, 0, roleid, false);

InvokeObjectPostAlterHookArg: 调用对象后修改钩子,通知其他模块设置已修改。

systable_endscan(scan); table_close(rel, NoLock); }

systable_endscan: 结束系统表的扫描。
table_close: 关闭系统表,但保持锁直到事务提交。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论