一、概述
Grant 在 He3DB 中用于用于执行SQL授权语句的函数,具体来说,它处理GRANT语句,用于赋予用户或角色特定的权限。
二、GrantRole 命令的执行流程
- PostgresMain
- exec_simple_query →执行简单的 SQL 查询;
- StartTransactionCommand → 开始事务;
- pg_parse_query →解析为内部的抽象语法树(AST);
- PortalRun
- standard_ProcessUtility →权限检查和准备;
- ExecuteGrantStm→授予或撤销用户对数据库的权限;
- CommandCounterIncrement→增量更新当前的命令计数器;
- CommitTransactionCommand→ 提交当前事务;
- finish_xact_command→ 在事务结束时,执行必要的清理和关闭操作;
三、核心结构体介绍
(一) 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,并更新系统目录和共享依赖信息。整个过程确保了权限操作的正确性和一致性。