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

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

cxp 2024-12-14
44

一、概述

   Grant 在 He3DB 中用于用于执行SQL授权语句的函数,具体来说,它处理GRANT语句,用于赋予用户或角色特定的权限。

二、GrantRole 命令的执行流程

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

三、核心结构体介绍

 (一)  GrantStmt 是一个表示 GRANT 或 REVOKE 语句的结构体。 该结构体用于存储和处理对数据库对象权限的授予或撤销信息。。以下是每个变量的详细解释:

typedef struct GrantStmt { NodeTag type; bool is_grant; /* true = GRANT, false = REVOKE */ GrantTargetType targtype; /* type of the grant target */ ObjectType objtype; /* kind of object being operated on */ List *objects; /* list of RangeVar nodes, ObjectWithArgs * nodes, or plain names (as String values) */ List *privileges; /* list of AccessPriv nodes */ /* privileges == NIL denotes ALL PRIVILEGES */ List *grantees; /* list of RoleSpec nodes */ bool grant_option; /* grant or revoke grant option */ RoleSpec *grantor; DropBehavior behavior; /* drop behavior (for REVOKE) */ } GrantStmt;
  • NodeTag type; 用于标识该节点的类型,通常在抽象语法树(AST)中用于区分不同节点的类型。
  • bool is_grant; 布尔值,指示该操作是 GRANT(true)还是 REVOKE(false)。
  • GrantTargetType targtype;表示授权目标的类型,通常可能是角色、数据库、表等。该类型可能是枚举类型,具体定义在某个头文件中。
  • ObjectType objtype; 表示被操作对象的类型,比如表、视图、序列等,这通常是一个枚举类型。
  • List *objects; 表示授权的目标对象列表,这里可能包含多个 RangeVar(范围变量),ObjectWithArgs(带参数的对象)或普通的字符串(对象名称)。
  • List *privileges;表示要授予或撤销的权限列表。每一个权限可能用 AccessPriv 类型表示。如果该列表为 NIL,表示是授予/撤销所有权限。
  • List *grantees; 表示接收授予或撤销权限的角色列表,这里使用 RoleSpec 类型表示角色。
  • bool grant_option; 布尔值,指示是否授予或撤销授予权限的选项。如果为 true,表示可以将权限进一步授予给其他角色。
  • RoleSpec *grantor;表示进行权限授予或撤销操作的执行者角色。
  • DropBehavior behavior;表示在 REVOKE操作时的删除行为,包括是否强制(CASCADE)或继续保持依赖关系(RESTRICT)。

InternalGrant 结构体是数据库系统内部用于处理权限操作的数据结构。与 GrantStmt 相比,它对权限的表示方式更为简洁和直接,使用 AclMode 类型表示权限位掩码,而不是一个权限列表。这种设计更适合在数据库内部进行高效的权限处理和操作。

typedef struct { bool is_grant; ObjectType objtype; List *objects; bool all_privs; AclMode privileges; List *col_privs; List *grantees; bool grant_option; DropBehavior behavior; } InternalGrant;
  • is_grant: 指示操作类型(GRANT 或 REVOKE)。
  • objtype: 被操作对象的类型。
  • objects:被操作的对象列表。
  • all_privs: 是否授予或撤销所有权限。
  • privileges: 具体的权限模式(位掩码形式)。
  • col_privs: 列级权限列表。
  • grantees: 被授予或撤销权限的角色列表。
  • grant_option: 是否授予或撤销授予权限的选项。
  • behavior: REVOKE 操作时的删除行为。

四、核心代码解析

   在 He3DB 的源代码中, ExecuteGrantStmt (@src\backend\catalog\aclchk.c)的函数,用于处理 PostgreSQL 中的 GRANT 和 REVOKE 语句。它将输入的 GrantStmt 结构转换为内部处理的 InternalGrant 结构,然后执行权限授予或撤销操作。以下是对代码中每个部分的详细解析:

void ExecuteGrantStmt(GrantStmt *stmt) {

函数名为 ExecuteGrantStmt,接受一个 GrantStmt 类型的指针 stmt 作为参数。

InternalGrant istmt; ListCell *cell = NULL; const char *errormsg = NULL; AclMode all_privileges;

istmt:用于存储转换后的内部授权语句。
cell:用于遍历链表的指针。
errormsg:用于存储错误信息。
all_privileges:用于存储所有可能的权限。

if (stmt->grantor) { Oid grantor = 0; grantor = get_rolespec_oid(stmt->grantor, false); /* * Currently, this clause is only for SQL compatibility, not very * interesting otherwise. */ if (grantor != GetUserId()) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("grantor must be current user"))); }

如果 stmt->grantor 不为空,则获取 grantor 的 Oid。
如果 grantor 不是当前用户,则抛出一个错误,表示 grantor 必须是当前用户。

/* * Turn the regular GrantStmt into the InternalGrant form. */ istmt.is_grant = stmt->is_grant; istmt.objtype = stmt->objtype;

将 stmt->is_grant 和 stmt->objtype 直接赋值给 istmt 的相应字段。

/* Collect the OIDs of the target objects */ switch (stmt->targtype) { case ACL_TARGET_OBJECT: istmt.objects = objectNamesToOids(stmt->objtype, stmt->objects, stmt->is_grant); break; case ACL_TARGET_ALL_IN_SCHEMA: istmt.objects = objectsInSchemaToOids(stmt->objtype, stmt->objects); break; /* ACL_TARGET_DEFAULTS should not be seen here */ default: elog(ERROR, "unrecognized GrantStmt.targtype: %d", (int) stmt->targtype); }

根据 stmt->targtype 的不同,调用不同的函数将对象名转换为 OID。
如果 targtype 无法识别,则抛出错误。

/* all_privs to be filled below */ /* privileges to be filled below */ istmt.col_privs = NIL; /* may get filled below */ istmt.grantees = NIL; /* filled below */ istmt.grant_option = stmt->grant_option; istmt.behavior = stmt->behavior;

初始化 istmt 的其他字段,如 col_privs、grantees 等。

/* * Convert the RoleSpec list into an Oid list. Note that at this point we * insert an ACL_ID_PUBLIC into the list if appropriate, so downstream * there shouldn't be any additional work needed to support this case. */ foreach(cell, stmt->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; } istmt.grantees = lappend_oid(istmt.grantees, grantee_uid); }

遍历 stmt->grantees 链表,将其中的每个 grantee 转换为 Oid,并添加到 istmt.grantees 列表中。
如果 grantee->roletype 是 ROLESPEC_PUBLIC,则将 ACL_ID_PUBLIC 添加到 grantees 列表中。

/* * Convert stmt->privileges, a list of AccessPriv nodes, into an AclMode * bitmask. Note: objtype can't be OBJECT_COLUMN. */ switch (stmt->objtype) { case OBJECT_TABLE: all_privileges = ACL_ALL_RIGHTS_RELATION | ACL_ALL_RIGHTS_SEQUENCE; errormsg = gettext_noop("invalid privilege type %s for relation"); break; case OBJECT_SEQUENCE: all_privileges = ACL_ALL_RIGHTS_SEQUENCE; errormsg = gettext_noop("invalid privilege type %s for sequence"); break; case OBJECT_DATABASE: all_privileges = ACL_ALL_RIGHTS_DATABASE; errormsg = gettext_noop("invalid privilege type %s for database"); break; case OBJECT_DOMAIN: all_privileges = ACL_ALL_RIGHTS_TYPE; errormsg = gettext_noop("invalid privilege type %s for domain"); break; case OBJECT_FUNCTION: all_privileges = ACL_ALL_RIGHTS_FUNCTION; errormsg = gettext_noop("invalid privilege type %s for function"); break; case OBJECT_LANGUAGE: all_privileges = ACL_ALL_RIGHTS_LANGUAGE; errormsg = gettext_noop("invalid privilege type %s for language"); break; case OBJECT_LARGEOBJECT: all_privileges = ACL_ALL_RIGHTS_LARGEOBJECT; errormsg = gettext_noop("invalid privilege type %s for large object"); break; case OBJECT_SCHEMA: all_privileges = ACL_ALL_RIGHTS_SCHEMA; errormsg = gettext_noop("invalid privilege type %s for schema"); break; case OBJECT_PROCEDURE: all_privileges = ACL_ALL_RIGHTS_FUNCTION; errormsg = gettext_noop("invalid privilege type %s for procedure"); break; case OBJECT_ROUTINE: all_privileges = ACL_ALL_RIGHTS_FUNCTION; errormsg = gettext_noop("invalid privilege type %s for routine"); break; case OBJECT_TABLESPACE: all_privileges = ACL_ALL_RIGHTS_TABLESPACE; errormsg = gettext_noop("invalid privilege type %s for tablespace"); break; case OBJECT_TYPE: all_privileges = ACL_ALL_RIGHTS_TYPE; errormsg = gettext_noop("invalid privilege type %s for type"); break; case OBJECT_FDW: all_privileges = ACL_ALL_RIGHTS_FDW; errormsg = gettext_noop("invalid privilege type %s for foreign-data wrapper"); break; case OBJECT_FOREIGN_SERVER: all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER; errormsg = gettext_noop("invalid privilege type %s for foreign server"); break; case OBJECT_PARAMETER_ACL: all_privileges = ACL_ALL_RIGHTS_PARAMETER_ACL; errormsg = gettext_noop("invalid privilege type %s for parameter"); break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) stmt->objtype); /* keep compiler quiet */ all_privileges = ACL_NO_RIGHTS; errormsg = NULL; }

根据 stmt->objtype 的不同,设置 all_privileges 和 errormsg。
如果 objtype 无法识别,则抛出错误。

if (stmt->privileges == NIL) { istmt.all_privs = true; /* * will be turned into ACL_ALL_RIGHTS_* by the internal routines * depending on the object type */ istmt.privileges = ACL_NO_RIGHTS; } else { istmt.all_privs = false; istmt.privileges = ACL_NO_RIGHTS; foreach(cell, stmt->privileges) { AccessPriv *privnode = (AccessPriv *) lfirst(cell); AclMode priv; if (privnode->cols) { if (stmt->objtype != OBJECT_TABLE) ereport(ERROR, (errcode(ERRCODE_INVALID_GRANT_OPERATION), errmsg("column privileges are only valid for relations"))); istmt.col_privs = lappend(istmt.col_privs, privnode); continue; } if (privnode->priv_name == NULL) /* parser mistake? */ elog(ERROR, "AccessPriv node must specify privilege or columns"); 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)))); istmt.privileges |= priv; } }

如果 stmt->privileges 为空,则表示授予所有权限。
否则,遍历 stmt->privileges 列表,将每个权限转换为 AclMode 位掩码,并检查权限是否有效。

ExecGrantStmt_oids(&istmt); }

调用 ExecGrantStmt_oids 函数,实际执行权限授予或撤销操作。

ExecGrantStmt_oids用于执行 GRANT 和 REVOKE 语句的内部函数,它根据对象类型调用不同的函数来处理权限的授予或撤销。下面是对每块代码的详细解释:

/* * ExecGrantStmt_oids * * Internal entry point for granting and revoking privileges. */ static void ExecGrantStmt_oids(InternalGrant *istmt) { switch (istmt->objtype) { case OBJECT_TABLE: case OBJECT_SEQUENCE: ExecGrant_Relation(istmt); break; case OBJECT_DATABASE: ExecGrant_Database(istmt); break; case OBJECT_DOMAIN: case OBJECT_TYPE: ExecGrant_Type(istmt); break; case OBJECT_FDW: ExecGrant_Fdw(istmt); break; case OBJECT_FOREIGN_SERVER: ExecGrant_ForeignServer(istmt); break; case OBJECT_FUNCTION: case OBJECT_PROCEDURE: case OBJECT_ROUTINE: ExecGrant_Function(istmt); break; case OBJECT_LANGUAGE: ExecGrant_Language(istmt); break; case OBJECT_LARGEOBJECT: ExecGrant_Largeobject(istmt); break; case OBJECT_SCHEMA: ExecGrant_Namespace(istmt); break; case OBJECT_TABLESPACE: ExecGrant_Tablespace(istmt); break; case OBJECT_PARAMETER_ACL: ExecGrant_Parameter(istmt); break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) istmt->objtype); } /* * Pass the info to event triggers about the just-executed GRANT. Note * that we prefer to do it after actually executing it, because that gives * the functions a chance to adjust the istmt with privileges actually * granted. */ if (EventTriggerSupportsObjectType(istmt->objtype)) EventTriggerCollectGrant(istmt); }

这段代码的主要作用是根据不同的数据库对象类型(如表、序列、数据库、类型等)来执行相应的权限授予或撤销操作。每个对象类型都有特定的函数来处理其权限操作。最后,如果对象类型支持事件触发器,则会触发相应的事件触发器来处理权限变更的事件。

  • 表和序列: 调用 ExecGrant_Relation。
  • 数据库: 调用 ExecGrant_Database。
  • 域和类型: 调用ExecGrant_Type。
  • 外部数据包装器: 调用 ExecGrant_Fdw。
  • 外部服务器: 调用ExecGrant_ForeignServer。
  • 函数、过程和例程: 调用 ExecGrant_Function。
  • 语言: 调用ExecGrant_Language。
  • 大对象: 调用 ExecGrant_Largeobject。
  • 模式: 调用ExecGrant_Namespace。
  • 表空间: 调用 ExecGrant_Tablespace。
  • 参数 ACL: 调用 ExecGrant_Parameter。
  • 默认情况: 如果对象类型不在上述任何情况中,则记录一个错误日志并终止执行。

以ExecGrant_Database为例:ExecGrant_Database 主要用于在 PostgreSQL 中授予或撤销数据库的权限。下面是对每一块代码的详细解析:

static void ExecGrant_Database(InternalGrant *istmt) { Relation relation; ListCell *cell = NULL;

函数声明,接收一个 InternalGrant 结构体的指针 istmt,用于处理权限操作。初始化变量 relation 和 cell。

if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS) istmt->privileges = ACL_ALL_RIGHTS_DATABASE;

如果 istmt->all_privs 为真且 istmt->privileges 为 ACL_NO_RIGHTS,则将 istmt->privileges 设置为 ACL_ALL_RIGHTS_DATABASE,表示授予所有数据库权限。

relation = table_open(DatabaseRelationId, RowExclusiveLock);

打开数据库关系表 pg_database,并获取一个行锁(RowExclusiveLock),以确保在修改期间数据的一致性。

foreach(cell, istmt->objects) { Oid datId = lfirst_oid(cell); Form_pg_database pg_database_tuple; Datum aclDatum; bool isNull = false; AclMode avail_goptions; AclMode this_privileges; Acl *old_acl = NULL; Acl *new_acl = NULL; Oid grantorId = 0; Oid ownerId = 0; HeapTuple newtuple; Datum values[Natts_pg_database] = {0}; bool nulls[Natts_pg_database] = {0}; bool replaces[Natts_pg_database] = {0}; int noldmembers = 0; int nnewmembers = 0; Oid *oldmembers = NULL; Oid *newmembers = NULL; HeapTuple tuple;

遍历 istmt->objects 列表中的每个数据库对象,初始化相关变量。

tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(datId)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for database %u", datId); pg_database_tuple = (Form_pg_database) GETSTRUCT(tuple);

通过缓存查找指定的数据库对象(datId),并获取其元组 pg_database_tuple。如果查找失败,则记录错误日志并终止执行。

/* * Get owner ID and working copy of existing ACL. If there's no ACL, * substitute the proper default. */ ownerId = pg_database_tuple->datdba; aclDatum = heap_getattr(tuple, Anum_pg_database_datacl, RelationGetDescr(relation), &isNull); if (isNull) { old_acl = acldefault(OBJECT_DATABASE, ownerId); /* There are no old member roles according to the catalogs */ noldmembers = 0; oldmembers = NULL; } else { old_acl = DatumGetAclPCopy(aclDatum); /* Get the roles mentioned in the existing ACL */ noldmembers = aclmembers(old_acl, &oldmembers); }

获取数据库的所有者 ownerId 和当前的 ACL old_acl。如果数据库没有 ACL,则使用默认的 ACL。同时获取旧的 ACL 成员角色。

/* Determine ID to do the grant as, and available grant options */ select_best_grantor(GetUserId(), istmt->privileges, old_acl, ownerId, &grantorId, &avail_goptions);

确定执行授予权限的用户 grantorId,并获取可用的授予选项 avail_goptions。

/* * Restrict the privileges to what we can actually grant, and emit the * standards-mandated warning and error messages. */ this_privileges = restrict_and_check_grant(istmt->is_grant, avail_goptions, istmt->all_privs, istmt->privileges, datId, grantorId, OBJECT_DATABASE, NameStr(pg_database_tuple->datname), 0, NULL);

限制实际可以授予的权限 this_privileges,并检查权限是否符合标准要求。如果权限不符合要求,则发出警告或错误消息。

/* * We need the members of both old and new ACLs so we can correct the * shared dependency information. */ nnewmembers = aclmembers(new_acl, &newmembers);

获取新的 ACL new_acl 中的成员角色,并存储在 newmembers 数组中。

/* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); MemSet(nulls, false, sizeof(nulls)); MemSet(replaces, false, sizeof(replaces)); replaces[Anum_pg_database_datacl - 1] = true; values[Anum_pg_database_datacl - 1] = PointerGetDatum(new_acl); newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces); CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);

将新的 ACL new_acl 插入到数据库元组中,并更新系统目录。

/* Update the shared dependency ACL info */ updateAclDependencies(DatabaseRelationId, pg_database_tuple->oid, 0, ownerId, noldmembers, oldmembers, nnewmembers, newmembers);

更新共享依赖信息,包括旧的和新的 ACL 成员角色。

ReleaseSysCache(tuple); pfree(new_acl); new_acl = NULL;

释放缓存和内存资源。

/* prevent error when processing duplicate objects */ CommandCounterIncrement(); }

增加命令计数器,避免在处理重复对象时出现错误。

table_close(relation, RowExclusiveLock); }

关闭数据库关系表 pg_database,并释放行锁。
这段代码的主要作用是根据传入的 InternalGrant 结构体,授予或撤销特定数据库的权限。它通过查找数据库元组,获取当前的 ACL,生成新的 ACL,并更新系统目录和共享依赖信息。整个过程确保了权限操作的正确性和一致性。

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

评论