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

0 阅读11分钟

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

一、概述

   AlterDefaultPrivileges 用来处理 He3DB 中的 ALTER DEFAULT PRIVILEGES 语句的执行。该语句用于修改默认的权限设置,控制未来创建的对象(如表、函数等)的权限。

二、GrantRole 命令的执行流程

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

图1 ALTER DEFAULT PRIVILEGES 命令的执行流程图

三、核心结构体介绍

 (一)  AlterDefaultPrivilegesStmt 是一个在数据库管理系统中用于定义修改默认权限操作的结构体。以下是对这个结构体的详细解析:

typedef struct AlterDefaultPrivilegesStmt
{
NodeTag type;
List options; / list of DefElem */
GrantStmt action; / GRANT/REVOKE action (with objects=NIL) */
} AlterDefaultPrivilegesStmt;

类型: NodeTag 含义: 这是一个标识结构体类型的字段。NodeTag 通常是一个枚举类型,用于表示不同的语法节点类型。在这里,type 字段标识了这个结构体是一个 AlterDefaultPrivilegesStmt 类型的语法节点。 List *options;

类型: List 含义: 这是一个指向 List 类型的指针。List 是一个链表结构,通常用于存储一系列的项。在这个结构体中,options 字段用于存储一系列的 DefElem(定义元素),这些 DefElem 可能包含关于默认权限的选项或参数。 GrantStmt action;

类型: GrantStmt * 含义: 这是一个指向 GrantStmt 类型的指针。GrantStmt 结构体用于表示 GRANT 或 REVOKE 操作的语法节点。在这个结构体中,action 字段指向一个 GrantStmt,表示对默认权限进行 GRANT 或 REVOKE 操作。需要注意的是,GrantStmt 中的 objects

typedef struct
{
Oid roleid; /* owning role /
Oid nspid; /
namespace, or InvalidOid if none /
/
remaining fields are same as in InternalGrant: */
bool is_grant;
ObjectType objtype;
bool all_privs;
AclMode privileges;
List *grantees;
bool grant_option;
DropBehavior behavior;
} InternalDefaultACL;

Oid roleid; 类型: Oid 含义: 这是一个对象标识符(OID),表示拥有这些默认权限的角色(用户或角色)。Oid 是 PostgreSQL 中用于唯一标识数据库对象的数据类型。

Oid nspid; 类型: Oid 含义: 这是一个对象标识符(OID),表示这些默认权限所关联的命名空间(schema)。如果 nspid 是 InvalidOid,则表示这些默认权限没有绑定到特定的命名空间。

bool is_grant; 类型: bool 含义: 这是一个布尔值,表示当前操作是授予权限(true)还是撤销权限(false)。

ObjectType objtype; 类型: ObjectType 含义: 这是一个枚举类型,表示受影响的对象类型(如表、序列、函数等)。ObjectType 定义了各种数据库对象类型。

bool all_privs; 类型: bool 含义: 这是一个布尔值,表示是否授予或撤销所有可用权限。如果为 true,则授予或撤销所有权限;如果为 false,则只授予或撤销指定的权限。

AclMode privileges; 类型: AclMode 含义: 这是一个表示权限模式的位掩码(bitmask),定义了具体的权限集合(如 SELECT, INSERT, UPDATE, DELETE 等)。AclMode 是一个用于表示权限的枚举类型。

List grantees; 类型: List 含义: 这是一个指向链表 List 的指针,存储了被授予或撤销权限的角色(用户或角色)。List 是一个链表结构,通常用于存储一系列的项。

bool grant_option; 类型: bool 含义: 这是一个布尔值,表示是否包含授予权限的选项。如果为 true,则被授权者还可以将这些权限授予其他用户;如果为 false,则不能。

DropBehavior behavior; 类型: DropBehavior 含义: 这是一个枚举类型,定义了在撤销权限时的行为。例如,DropBehavior 可以表示是否递归撤销权限,或者是否只撤销当前层级的权限。

四、核心代码解析

   在 He3DB 的源代码中,ExecAlterDefaultPrivilegesStmt 是用来处理 He3DB 中的 ALTER DEFAULT PRIVILEGES 语句的执行。该语句用于修改默认的权限设置,即在创建新对象时自动应用的权限。以下是对每块代码的详细解析:

void ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *stmt)

{ GrantStmt *action = stmt->action;

InternalDefaultACL iacls;

ListCell *cell = NULL;

List *rolespecs = NIL;

List *nspnames = NIL;

DefElem *drolespecs = NULL;

DefElem *dnspnames = NULL;

AclMode all_privileges;

const char *errormsg = NULL;

  • action:指向 stmt->action,表示操作(如授予或撤销权限)。
  • iacls:InternalDefaultACL结构体,用于存储默认权限的内部表示。
  • rolespecs 和 nspnames:分别存储角色和模式的列表。
  • drolespecs 和dnspnames:分别指向角色和模式的定义元素。
  • all_privileges:存储所有可能的权限。errormsg:存储错误消息的指针。

foreach(cell, stmt->options)
{
DefElem *defel = (DefElem *) lfirst(cell);

if (strcmp(defel->defname, "schemas") == 0)
{
if (dnspnames)
errorConflictingDefElem(defel, pstate);
dnspnames = defel;
}
else if (strcmp(defel->defname, "roles") == 0)
{
if (drolespecs)
errorConflictingDefElem(defel, pstate);
drolespecs = defel;
}
else
elog(ERROR, "option "%s" not recognized", defel->defname);
}
if (dnspnames)
nspnames = (List *) dnspnames->arg;
if (drolespecs)
rolespecs = (List *) drolespecs->arg;

遍历 stmt->options 列表,解析选项中的模式 (schemas) 和角色 (roles)。如果有冲突的定义元素,则调用 errorConflictingDefElem 函数报错。将解析出的模式和角色列表存储在 nspnames 和 rolespecs 中。

ciacls.is_grant = action->is_grant;

iacls.objtype = action->objtype;

iacls.grantees = NIL; /* filled below */

iacls.grant_option = action->grant_option;

iacls.behavior = action->behavior;

设置 iacls 结构体的字段,包括是否为授予操作 (is_grant)、对象类型 (objtype)、是否带有授予选项 (grant_option) 以及行为 (behavior)。

foreach(cell, action->grantees)
{
RoleSpec *grantee = (RoleSpec *) lfirst(cell);
Oid grantee_uid = 0;

switch (grantee->roletype)
{
case ROLESPEC_PUBLIC:
grantee_uid = ACL_ID_PUBLIC;
break;
default:
grantee_uid = get_rolespec_oid(grantee, false);
break;
}
iacls.grantees = lappend_oid(iacls.grantees, grantee_uid);
}

遍历 action->grantees,将 RoleSpec 转换为 Oid,并添加到 iacls.grantees 列表中。如果 grantee 是 ROLESPEC_PUBLIC,则使用 ACL_ID_PUBLIC。

switch (action->objtype)
{
case OBJECT_TABLE:
all_privileges = ACL_ALL_RIGHTS_RELATION;
errormsg = gettext_noop("invalid privilege type %s for relation");
break;
// 其他对象类型的处理...
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) action->objtype);
all_privileges = ACL_NO_RIGHTS;
errormsg = NULL;
}

if (action->privileges == NIL)
{
iacls.all_privs = true;
iacls.privileges = ACL_NO_RIGHTS;
}
else
{
iacls.all_privs = false;
iacls.privileges = ACL_NO_RIGHTS;

foreach(cell, action->privileges)
{
AccessPriv *privnode = (AccessPriv *) lfirst(cell);
AclMode priv;

if (privnode->cols)
ereport(ERROR, (errcode(ERRCODE_INVALID_GRANT_OPERATION), errmsg("default privileges cannot be set for columns")));

if (privnode->priv_name == NULL)
elog(ERROR, "AccessPriv node must specify privilege");
priv = string_to_privilege(privnode->priv_name);

if (priv & ~((AclMode) all_privileges))
ereport(ERROR, (errcode(ERRCODE_INVALID_GRANT_OPERATION), errmsg(errormsg, privilege_to_string(priv))));

iacls.privileges |= priv;
}
}

根据 action->objtype 设置 all_privileges 和 errormsg。如果 action->privileges 为空,则设置 iacls.all_privs 为 true,表示授予所有权限。否则,遍历 action->privileges,将权限字符串转换为 AclMode 位掩码,并设置 iacls.privileges。

if (rolespecs == NIL)
{
iacls.roleid = GetUserId();
SetDefaultACLsInSchemas(&iacls, nspnames);
}
else
{
foreach(rolecell, rolespecs)
{
RoleSpec *rolespec = lfirst(rolecell);
iacls.roleid = get_rolespec_oid(rolespec, false);
check_is_member_of_role(GetUserId(), iacls.roleid);
SetDefaultACLsInSchemas(&iacls, nspnames);
}
}

如果没有指定角色 (rolespecs 为空),则使用当前用户 (GetUserId()) 作为角色。否则,遍历 rolespecs,获取角色的 Oid,并检查当前用户是否是该角色的成员。调用SetDefaultACLsInSchemas 函数,为指定的模式 (nspnames) 设置默认权限。    这段代码的主要功能是解析 ALTER DEFAULT PRIVILEGES 命令的各个部分,将解析出的数据填充到 InternalDefaultACL 结构体中,并最终调用 SetDefaultACLsInSchemas 函数设置默认权限。代码中涉及了角色、模式、权限的解析和转换,确保命令的正确执行。

   SetDefaultACLsInSchemas主要用于设置默认访问控制列表(ACL)与特定模式(schema)相关联。

static void
SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List nspnames)
{
if (nspnames == NIL)
{
/
Set database-wide permissions if no schema was specified */
iacls->nspid = InvalidOid;

SetDefaultACL(iacls);
}

nspnames == NIL:检查传入的模式名称列表是否为空。 如果是空,则表示没有指定特定的模式,这种情况下: iacls->nspid = InvalidOid:将 iacls 的 nspid 字段设置为 InvalidOid,通常这表示“无模式”或数据库级别的 ACL。 SetDefaultACL(iacls):调用 SetDefaultACL 函数,设置数据库范围的默认 ACL。

else
{
/* Look up the schema OIDs and set permissions for each one */
ListCell *nspcell = NULL;

foreach(nspcell, nspnames)
{
char *nspname = strVal(lfirst(nspcell));

iacls->nspid = get_namespace_oid(nspname, false);
SetDefaultACL(iacls);
}
}
}

else:如果 nspnames 不为空,接下来将为每个指定的模式设置 ACL。 *ListCell nspcell = NULL:定义一个列表单元指针,用于遍历模式名称列表。 foreach(nspcell, nspnames):遍历 nspnames 列表,每次循环中 nspcell 指向当前列表单元。 lfirst(nspcell):获取当前单元的值(即模式的名称)。 strVal(…):将值转换为字符串类型。 iacls->nspid = get_namespace_oid(nspname, false):通过模式名称获取模式的 OID(对象标识符),并将其赋值给 iacls->nspid。第二个参数 false 表示在找不到名称时不引发错误。

要求目标角色在模式上具有 CREATE 权限,因为没有该权限,角色将无法创建对象,从而导致 ACL 无法应用。 现在:取消此要求,仅需要 ALTER 权限。这样做是因为之前的检查可能引起困惑,也可能导致某些数据库状态在转储或还原时失败。 SetDefaultACL(iacls):在这里调用 SetDefaultACL 函数来为当前模式设置这些默认的 ACL。

static void SetDefaultACL(InternalDefaultACL *iacls)

static void: 表示该函数是静态的,只在当前文件中可见。 SetDefaultACL: 函数名。 InternalDefaultACL *iacls: 输入参数,是一个指向InternalDefaultACL结构体的指针,包含默认ACL的相关信息。

AclMode this_privileges = iacls->privileges;
char objtype = 0;
Relation rel;
HeapTuple tuple;
bool isNew = false;
Acl *def_acl = NULL;
Acl *old_acl = NULL;
Acl *new_acl = NULL;
HeapTuple newtuple;
Datum values[Natts_pg_default_acl] = {0};
bool nulls[Natts_pg_default_acl] = {0};
bool replaces[Natts_pg_default_acl] = {0};
int noldmembers = 0;
int nnewmembers = 0;
Oid *oldmembers = NULL;
Oid *newmembers = NULL;

this_privileges: 存储当前的权限。 objtype: 存储对象类型。 rel: 关系描述符。 tuple: 存储从系统目录中找到的元组。 isNew: 标记是否是新建的ACL。 def_acl, old_acl, new_acl: 存储ACL的指针。 newtuple: 存储新创建的元组。 values, nulls, replaces: 用于存储元组的值、空值、替换标志。 noldmembers, nnewmembers: 存储旧和新ACL中的成员数量。 oldmembers, newmembers: 存储旧和新ACL中的成员ID。

rel = table_open(DefaultAclRelationId, RowExclusiveLock);

table_open: 打开pg_default_acl表,获取一个行级排他锁。

if (!OidIsValid(iacls->nspid))
def_acl = acldefault(iacls->objtype, iacls->roleid);
else
def_acl = make_empty_acl();

如果iacls->nspid无效(即未指定命名空间),则调用acldefault获取默认ACL。 否则,创建一个空的ACL。

switch (iacls->objtype)
{
case OBJECT_TABLE:
objtype = DEFACLOBJ_RELATION;
if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
this_privileges = ACL_ALL_RIGHTS_RELATION;
break;

case OBJECT_SEQUENCE:
objtype = DEFACLOBJ_SEQUENCE;
if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
this_privileges = ACL_ALL_RIGHTS_SEQUENCE;
break;

case OBJECT_FUNCTION:
objtype = DEFACLOBJ_FUNCTION;
if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
this_privileges = ACL_ALL_RIGHTS_FUNCTION;
break;

case OBJECT_TYPE:
objtype = DEFACLOBJ_TYPE;
if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
this_privileges = ACL_ALL_RIGHTS_TYPE;
break;

case OBJECT_SCHEMA:
if (OidIsValid(iacls->nspid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("cannot use IN SCHEMA clause when using GRANT/REVOKE ON SCHEMAS")));
objtype = DEFACLOBJ_NAMESPACE;
if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
this_privileges = ACL_ALL_RIGHTS_SCHEMA;
break;

default:
elog(ERROR, "unrecognized objtype: %d",
(int) iacls->objtype);
objtype = 0; /* keep compiler quiet */
break;
}

根据iacls->objtype设置objtype,并根据iacls->all_privs和this_privileges设置权限。 如果是OBJECT_SCHEMA且指定了命名空间,则报错。 默认情况下,报错并退出。

tuple = SearchSysCache3(DEFACLROLENSPOBJ,
ObjectIdGetDatum(iacls->roleid),
ObjectIdGetDatum(iacls->nspid),
CharGetDatum(objtype));

if (HeapTupleIsValid(tuple))
{
Datum aclDatum;
bool isNull = false;

aclDatum = SysCacheGetAttr(DEFACLROLENSPOBJ, tuple,
Anum_pg_default_acl_defaclacl,
&isNull);
if (!isNull)
old_acl = DatumGetAclPCopy(aclDatum);
else
old_acl = NULL;
isNew = false;
}
else
{
old_acl = NULL;
isNew = true;
}

使用SearchSysCache3查找系统缓存中的元组。 如果找到元组,则获取old_acl。 否则,设置old_acl为NULL,并标记为新ACL。

if (old_acl != NULL)
{
noldmembers = aclmembers(old_acl, &oldmembers);
}
else
{
old_acl = aclcopy(def_acl);
noldmembers = 0;
oldmembers = NULL;
}

如果old_acl存在,获取其成员数量和ID。 否则,复制def_acl作为old_acl。

new_acl = merge_acl_with_grant(old_acl,
iacls->is_grant,
iacls->grant_option,
iacls->behavior,
iacls->grantees,
this_privileges,
iacls->roleid,
iacls->roleid);

调用merge_acl_with_grant函数,合并旧ACL和新权限。

aclitemsort(new_acl);
aclitemsort(def_acl);
if (aclequal(new_acl, def_acl))
{
if (!isNew)
{
ObjectAddress myself;

myself.classId = DefaultAclRelationId;
myself.objectId = ((Form_pg_default_acl) GETSTRUCT(tuple))->oid;
myself.objectSubId = 0;

performDeletion(&myself, DROP_RESTRICT, 0);
}
}
else
{
Oid defAclOid = 0;
/* Prepare to insert or update pg_default_acl entry */
MemSet(values, 0, sizeof(values));
MemSet(nulls, false, sizeof(nulls));
MemSet(replaces, false, sizeof(replaces));

对new_acl和def_acl进行排序。 如果new_acl和def_acl相等且不是新ACL,则删除旧ACL。

if (isNew)
{
defAclOid = GetNewOidWithIndex(rel, DefaultAclOidIndexId,
Anum_pg_default_acl_oid);
values[Anum_pg_default_acl_oid - 1] = ObjectIdGetDatum(defAclOid);
values[Anum_pg_default_acl_defaclrole - 1] = ObjectIdGetDatum(iacls->roleid);
values[Anum_pg_default_acl_defaclnamespace - 1] = ObjectIdGetDatum(iacls->nspid);
values[Anum_pg_default_acl_defaclobjtype - 1] = CharGetDatum(objtype);
values[Anum_pg_default_acl_defaclacl - 1] = PointerGetDatum(new_acl);

newtuple = heap_form_tuple(RelationGetDescr(rel), values, nulls);
CatalogTupleInsert(rel, newtuple);
}
else
{
defAclOid = ((Form_pg_default_acl) GETSTRUCT(tuple))->oid;
values[Anum_pg_default_acl_defaclacl - 1] = PointerGetDatum(new_acl);
replaces[Anum_pg_default_acl_defaclacl - 1] = true;

newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
values, nulls, replaces);
CatalogTupleUpdate(rel, &newtuple->t_self, newtuple);
}

如果是新ACL,则插入新元组。 否则,更新现有元组。

if (isNew)
{
recordDependencyOnOwner(DefaultAclRelationId, defAclOid,
iacls->roleid);

if (OidIsValid(iacls->nspid))
{
ObjectAddress myself,
referenced;

myself.classId = DefaultAclRelationId;
myself.objectId = defAclOid;
myself.objectSubId = 0;

referenced.classId = NamespaceRelationId;
referenced.objectId = iacls->nspid;
referenced.objectSubId = 0;

recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
}
}

记录新ACL的所有者依赖关系。 如果指定了命名空间,则记录命名空间的依赖关系。

nnewmembers = aclmembers(new_acl, &newmembers);
updateAclDependencies(DefaultAclRelationId,
defAclOid, 0,
iacls->roleid,
noldmembers, oldmembers,
nnewmembers, newmembers);

获取新ACL的成员数量和ID。 更新依赖关系。

if (isNw)
InvokeObjectPostCreateHook(DefaultAclRelationId, defAclOid, 0);
else
InvokeObjectPostAlterHook(DefaultAclRelationId, defAclOid, 0);

如果是新ACL,调用创建后钩子。 否则,调用修改后钩子。

if (HepTupleIsValid(tuple))
ReleaseSysCache(tuple);
table_close(rel, RowExclusiveLock);
CommandCounterIncrement();

释放系统缓存中的元组。 关闭关系表。 增加命令计数器。

该函数的主要功能是根据输入参数设置默认的ACL。它首先根据对象类型和权限生成新的ACL,然后将其与系统中的ACL进行合并、排序和比较。如果新的ACL与系统中的ACL不同,则更新或插入新的ACL,并记录相关的依赖关系。最后,释放资源并增加命令计数器。