在前面介绍过“9.1 安全管理整体架构和代码概览、9.2 安全认证”,本篇我们介绍第9章 安全管理源码解析中“9.3 角色管理”的相关精彩内容介绍。

9.3 角色管理

角色是拥有数据库对象和权限的实体,在不同的环境中角色可以认为是一个用户、一个组或者兼顾两者。角色管理包含了角色的创建、修改、删除、权限授予和回收操作。

9.3.1 角色创建

如果在openGauss上需要创建一个角色,可以使用SQL命令CREATE ROLE,其语法为:

CREATE ROLE role_name [ [ WITH ] option [ ... ] ] [ ENCRYPTED | UNENCRYPTED ] { PASSWORD | IDENTIFIED BY } { 'password' | DISABLE };

创建角色是通过函数CreateRole实现的,其函数接口为:

void CreateRole(CreateRoleStmt* stmt)

其中,CreateRoleStmt为创建角色时所需的数据结构,具体数据结构代码如下:

typedef struct CreateRoleStmt {NodeTag type;RoleStmtType stmt_type;  /* 将要创建的角色类型 ROLE/USER/GROUP  */char* role;              /* 角色名 */List* options;            /* 角色属性列表 */
} CreateRoleStmt;

字段stmt_type是枚举类型,相关代码如下:

typedef enum RoleStmtType {
ROLESTMT_ROLE,    /* 代表创建角色 */
ROLESTMT_USER,    /* 代表创建用户 */
ROLESTMT_GROUP,  /* 代表创建组用户 */
} RoleStmtType;

字段option用来存储角色的属性信息,具体的数据结构为:

typedef struct DefElem {NodeTag type;char* defnamespace;     /* 节点对应的命名空间  */char* defname;          /* 节点对应的角色属性名  */Node* arg;              /* 表示值或类型名  */DefElemAction defaction;  /* SET/ADD/DROP 等其他未指定的行为  */
} DefElem;

在上述的关键数据结构基础之上,完整的创建角色流程如图9-14所示。

图9-14 openGauss角色创建流程

创建角色时先判断所要创建的角色类型。如果是创建用户,则设置其canlogin属性为true,因为用户默认具有登录权限。而创建角色和创建组时,若角色属性参数没有声明的话,则canlogin属性默认为false。相关代码如下:

/* 默认值可能因原始语句类型而异 */
switch (stmt->stmt_type) {
case ROLESTMT_ROLE:break;case ROLESTMT_USER:canlogin = true;break;case ROLESTMT_GROUP:break;default:break;
}

检查完所要创建的角色类型以后,开始循环获取角色属性options中的内容,并将其转换成对应的角色属性值类型。相关代码如下:

/* 从node tree中获取option */
foreach (option, stmt->options) {DefElem* defel = (DefElem*)lfirst(option);if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 ||strcmp(defel->defname, "unencryptedPassword") == 0) {if (dpassword != NULL) {clean_role_password(dpassword);ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));}dpassword = defel;if (strcmp(defel->defname, "encryptedPassword") == 0)encrypt_password = true;else if (strcmp(defel->defname, "unencryptedPassword") == 0) {clean_role_password(dpassword);ereport(ERROR,(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),errmsg("Permission denied to create role with option UNENCRYPTED.")));}} else if (strcmp(defel->defname, "sysid") == 0) {ereport(NOTICE, (errmsg("SYSID can no longer be specified")));} else if (strcmp(defel->defname, "inherit") == 0) {if (dinherit != NULL) {clean_role_password(dpassword);ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));}dinherit = defel;} else if (strcmp(defel->defname, "createrole") == 0) {if (dcreaterole != NULL) {clean_role_password(dpassword);ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));}dcreaterole = defel;} else if (strcmp(defel->defname, "createdb") == 0) {if (dcreatedb != NULL) {clean_role_password(dpassword);ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));}dcreatedb = defel;} else if (strcmp(defel->defname, "useft") == 0) {if (duseft != NULL) {clean_role_password(dpassword);ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));}duseft = defel;
……

根据对应的参数信息转换需要的角色属性值类型,如提取issuper值和createrole值等。相关代码如下:

  if (dissuper != NULL)issuper = intVal(dissuper->arg) != 0;if (dinherit != NULL)inherit = intVal(dinherit->arg) != 0;if (dcreaterole != NULL)createrole = intVal(dcreaterole->arg) != 0;if (dcreatedb != NULL)createdb = intVal(dcreatedb->arg) != 0;……

在完成了转换以后,将角色属性值以及角色的信息一起构建一个pg_authid的元组,再写回系统表并更新索引。作相关代码如下:

/* 检查pg_authid relation,确认该角色没有存在*/
Relation pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);TupleDesc pg_authid_dsc = RelationGetDescr(pg_authid_rel);if (OidIsValid(get_role_oid(stmt->role, true))) {str_reset(password);ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("role \"%s\" already exists", stmt->role)));
}
……/* 创建一个插入的tuple */errno_t errorno = memset_s(new_record, sizeof(new_record), 0, sizeof(new_record));securec_check(errorno, "\0", "\0");errorno = memset_s(new_record_nulls, sizeof(new_record_nulls), false, sizeof(new_record_nulls));securec_check(errorno, "\0", "\0");new_record[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->role));new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);new_record[Anum_pg_authid_rolauditadmin - 1] = BoolGetDatum(isauditadmin);new_record[Anum_pg_authid_rolsystemadmin - 1] = BoolGetDatum(issystemadmin);
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
……HeapTuple tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);if (u_sess->proc_cxt.IsBinaryUpgrade && OidIsValid(u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid)) {HeapTupleSetOid(tuple, u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid);u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid = InvalidOid;}roleid = simple_heap_insert(pg_authid_rel, tuple);if (IsUnderPostmaster) {if (OidIsValid(rpoid) && (rpoid != DEFAULT_POOL_OID))recordDependencyOnRespool(AuthIdRelationId, roleid, rpoid);u_sess->wlm_cxt->wlmcatalog_update_user = true;
}
……

完成更新以后,将新创建的角色加入指定存在的父角色中。相关代码如下:

  /* 将新角色添加到指定的现有角色中 */foreach (item, addroleto) {char* oldrolename = strVal(lfirst(item));Oid oldroleid = get_role_oid(oldrolename, false);AddRoleMems(oldrolename, oldroleid, list_make1(makeString(stmt->role)), list_make1_oid(roleid), GetUserId(), false);}AddRoleMems(stmt->role, roleid, adminmembers, roleNamesToIds(adminmembers), GetUserId(), true);AddRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), GetUserId(), false);

至此就完成了整个角色创建的过程。

9.3.2 角色管理

1. 修改角色属性

修改一个数据库角色可以使用SQL命令ALTER ROLE。角色属性的修改是通过调用AlterRole函数来实现的,该函数只有一个类型为AlterRoleStmt结构的参数。相关代码如下:

typedef struct AlterRoleStmt {NodeTag  type;char*  role; /* 角色的名称 */List*  options; /* 需要修改的属性列表 */int  action;  /* +1增加成员关系, -1删除成员关系 */RoleLockType  lockstatus; /* 角色锁定状态 */
} AlterRoleStmt;

修改角色的流程如图9-15所示。

图9-15 openGauss角色管理流程图

调用函数AlterRole修改用户角色属性时,首先循环判断options,依次提取要修改的角色属性;然后查看系统表pg_authid判断是否已存在该角色,如果不存在则提示报错;再进行相应的权限判断,检查执行者是否有权限去更改该角色的属性;最后构建一个新的元组,将要更改的属性更新到新元组中,存入系统表pg_authid。同时AlterRole函数也可以用来调整角色的成员关系,结构体中的action字段值设置为1和-1分别表示增加和删除成员关系,该选项将在授予和回收角色章节具体描述。AlterRole函数的具体实现代码如下:

void AlterRole(AlterRoleStmt* stmt)
{. . ./* 循环提取角色的属性options */foreach (option, stmt->options) {DefElem* defel = (DefElem*)lfirst(option);if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 ||strcmp(defel->defname, "unencryptedPassword") == 0) {if (dpassword != NULL) {clean_role_password(dpassword);ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));}dpassword = defel;if (strcmp(defel->defname, "encryptedPassword") == 0)encrypt_password = true;else if (strcmp(defel->defname, "unencryptedPassword") == 0) {clean_role_password(dpassword);ereport(ERROR,(errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),errmsg("Permission denied to create role with option UNENCRYPTED.")));}} else if (strcmp(defel->defname, "createrole") == 0) {if (dcreaterole != NULL) {clean_role_password(dpassword);ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));}dcreaterole = defel;} else if (strcmp(defel->defname, "inherit") == 0) {if (dinherit != NULL) {clean_role_password(dpassword);ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));}dinherit = defel;}
. . .else {clean_role_password(dpassword);ereport(ERROR,(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("option \"%s\" not recognized", defel->defname)));}}
/* 将提取的属性赋值给对应的变量 */if (dpassword != NULL && dpassword->arg != NULL) {head = list_head((List*)dpassword->arg);if (head != NULL) {pwdargs = (A_Const*)linitial((List*)dpassword->arg);if (pwdargs != NULL) {password = strVal(&pwdargs->val);}if (lnext(head)) {pwdargs = (A_Const*)lsecond((List*)dpassword->arg);if (pwdargs != NULL) {replPasswd = strVal(&pwdargs->val);}}}}if (dinherit != NULL)inherit = intVal(dinherit->arg);if (dcreaterole != NULL)createrole = intVal(dcreaterole->arg);. . ./* 查看要修改的角色是否存在,不存在则提示报错 */Relation pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);HeapTuple tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(stmt->role));if (!HeapTupleIsValid(tuple)) {str_reset(password);str_reset(replPasswd);if (!have_createrole_privilege())ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));elseereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", stmt->role)));}
roleid = HeapTupleGetOid(tuple);
. . .
/* 检查是否有权限更改相应角色的属性,权限不足则提示报错 */if (roleid == BOOTSTRAP_SUPERUSERID) {if (!(issuper < 0 && inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 &&isauditadmin < 0 && issystemadmin < 0 && isvcadmin < 0 && useft < 0 && dconnlimit == NULL &&rolemembers == NULL && validBegin == NULL && validUntil == NULL && drespool == NULL &&dparent == NULL && dnode_group == NULL && dspacelimit == NULL && dtmpspacelimit == NULL &&dspillspacelimit == NULL)) {str_reset(password);str_reset(replPasswd);ereport(ERROR,(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),errmsg("Permission denied to change privilege of the initial account.")));}}if (dpassword != NULL && roleid == BOOTSTRAP_SUPERUSERID && GetUserId() != BOOTSTRAP_SUPERUSERID) {str_reset(password);str_reset(replPasswd);ereport(ERROR,(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),errmsg("Permission denied to change password of the initial account.")));}. . .} else if (!have_createrole_privilege()) {if (!(inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 && isauditadmin < 0 &&issystemadmin < 0 && isvcadmin < 0 && useft < 0 && dconnlimit == NULL && rolemembers == NULL &&validBegin == NULL && validUntil == NULL && !*respool && !OidIsValid(parentid) && dnode_group == NULL &&!spacelimit && !tmpspacelimit && !spillspacelimit &&/* if not superuser or have createrole privilege, permission of lock and unlock is denied */stmt->lockstatus == DO_NOTHING &&/* if alter password, it will be handled below */roleid == GetUserId()) ||(roleid != GetUserId() && dpassword == NULL)) {str_reset(password);str_reset(replPasswd);ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));}
}
.../* 将要更改的角色属性值分别更新到新元组中,再将新元组替代旧元组存入系统表pg_authid中 */if (issuper >= 0) {new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);new_record_repl[Anum_pg_authid_rolsuper - 1] = true;new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;}if (inherit >= 0) {new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);new_record_repl[Anum_pg_authid_rolinherit - 1] = true;}. . .HeapTuple new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record, new_record_nulls, new_record_repl);simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple);CatalogUpdateIndexes(pg_authid_rel, new_tuple);. . .
/* 判断成员关系,增加或删除成员 */if (stmt->action == 1) AddRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), GetUserId(), false);else if (stmt->action == -1) /* drop members FROM role */DelRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), false);. . .heap_close(pg_authid_rel, NoLock);
}

2. 删除角色

如果要删除一个角色,可以使用SQL命令DROP ROLE。角色的删除是通过调用DropRole函数来实现的,该函数只有一个类型为DropRoleStmt结构的参数。相关代码如下:

typedef struct DropRoleStmt {NodeTagtype;List*roles;               /*  要删除的角色列表  */boolmissing_ok;          /*  判断角色是否存在  */boolis_user;             /*  要删除的是角色还是用户  */boolinherit_from_parent;  /*  是否继承自父角色*/DropBehavior behavior;            /*  是否级联删除依赖对象  */
} DropRoleStmt;

删除角色的流程如图9-16所示。

图9-16 openGauss角色删除流程图

角色删除的执行流程为:首先判断当前操作者是否有权限执行该操作,若没有则报错退出;然后检查待删除的角色是否存在,若不存在,则根据missing_ok选择返回ERROR或NOTICE提示信息;再通过扫描系统表pg_authid和pg_auth_members,删除所有涉及待删除角色的元组执行;若behavior取值DROP_CASCADE,则级联删除该角色所拥有的所有数据库对象;最后删除该角色在系统表pg_auth_history和pg_user_status中对应的信息。具体的实现过程代码如下:

void DropRole(DropRoleStmt* stmt)
{. . .
/*  检查执行者是否有权限删除角色  */if (!have_createrole_privilege())ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied to drop role.")));
/*  循环处理要删除的角色  */foreach (item, stmt->roles) {
. . .
/*  检查要删除的角色是否存在,若不存在则提示报错  */HeapTuple tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));if (!HeapTupleIsValid(tuple)) {if (!stmt->missing_ok) {ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", role)));} else {ereport(NOTICE, (errmsg("role \"%s\" does not exist, skipping", role)));}continue;}. . .
/*  当前用户不允许删除  */if (roleid == GetUserId())ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("current user cannot be dropped")));if (roleid == GetOuterUserId())ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("current user cannot be dropped")));if (roleid == GetSessionUserId())ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("session user cannot be dropped")));/*  校验执行者和被删除角色的权限,如系统管理员才有权限删除其他系统管理员  */if((((Form_pg_authid)GETSTRUCT(tuple))->rolsuper|| ((Form_pg_authid)GETSTRUCT(tuple))->rolsystemadmin) &&!isRelSuperuser())ereport(ERROR,(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));if ((((Form_pg_authid)GETSTRUCT(tuple))->rolauditadmin) &&g_instance.attr.attr_security.enablePrivilegesSeparate && !isRelSuperuser())ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));. . ./*  针对CASCADE(级联)的情况,删除该角色拥有的对象  */if (stmt->behavior == DROP_CASCADE) {char* user = NULL;CancelQuery(role);user = (char*)palloc(sizeof(char) * strlen(role) + 1);errno_t errorno = strncpy_s(user, strlen(role) + 1, role, strlen(role));securec_check(errorno, "\0", "\0");drop_objectstmt.behavior = stmt->behavior;drop_objectstmt.type = T_DropOwnedStmt;drop_objectstmt.roles = list_make1(makeString(user));DropOwnedObjects(&drop_objectstmt);list_free_deep(drop_objectstmt.roles);}/*  检查是否有对象依赖于该角色,若还存在依赖,则提示报错  */if (checkSharedDependencies(AuthIdRelationId, roleid, &detail, &detail_log))ereport(ERROR,(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),errmsg("role \"%s\" cannot be dropped because some objects depend on it", role),errdetail_internal("%s", detail),errdetail_log("%s", detail_log)));/*  从相关系统表中删除涉及待删除角色的元组  */simple_heap_delete(pg_authid_rel, &tuple->t_self);
. . .while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan))) {simple_heap_delete(pg_auth_members_rel, &tmp_tuple->t_self);}systable_endscan(sscan);DropAuthHistory(roleid);DropUserStatus(roleid);DeleteSharedComments(roleid, AuthIdRelationId);DeleteSharedSecurityLabel(roleid, AuthIdRelationId);DropSetting(InvalidOid, roleid);
. . .heap_close(pg_auth_members_rel, NoLock);heap_close(pg_authid_rel, NoLock);
}

3. 授予和回收角色

如果要授予或回收角色的成员关系,可以使用SQL命令“GRANT/REVOKE”。如果声明了“WITH ADMIN OPTION”选项,那么被加入的成员角色还可以将其他角色加入到父角色中。角色的授予或回收通过调用GrantRole函数来实现,该函数只有一个类型为GrantRoleStmt结构的参数。相关代码如下:

typedef struct GrantRoleStmt {NodeTag type;List* granted_roles;/*  被授予或回收的角色集合  */List* grantee_roles;/*  从granted_roles中增加或删除的角色集合  */Bool is_grant;/*  true代表授权,false代表回收  */Bool admin_opt;/*  是否带有with admin option选项  */char* grantor;/*  授权者  */Drop Behaviorbehavior;/*  是否级联回收角色  */
} GrantRoleStmt;

授予角色时,grantee_roles中的角色将被添加到granted_roles,通过调用函数AddRoleMems实现;回收角色时,将grantee_roles中的角色从granted_roles中删除,通过调用函数DelRoleMems实现。
函数AddRoleMems的实现流程如图9-17所示。

图9-17 openGauss增加用户成员流程图

函数AddRoleMems的具体实现代码如下,其中:
(1) rolename和roleid分别表示要被加入成员的角色的名称和OID。
(2) memberNames和memberIds分别是要添加的角色名称和OID的列表。
(3) grantorId表示授权者的OID。
(4) admin_opt表示是否带有with admin option选项。

static void AddRoleMems(const char* rolename, Oid roleid, const List* memberNames, List* memberIds, Oid grantorId, bool admin_opt)
{
. . ./*  校验执行者的权限  */if (superuser_arg(roleid)) {if (!isRelSuperuser())ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));. . .}
. . .if (grantorId != GetUserId() && !superuser())ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to set grantor")));
/*  循环处理要添加的角色  */pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock);pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);forboth(nameitem, memberNames, iditem, memberIds){/*  针对角色和成员信息创建pg_auth_members元组,再将新元组插入到系统表中  */
. . .new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);new_record[Anum_pg_auth_members_member -1] = ObjectIdGetDatum(memberid);new_record[Anum_pg_auth_members_grantor -  1] = ObjectIdGetDatum(grantorId);new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);if (HeapTupleIsValid(authmem_tuple)) {new_record_repl[Anum_pg_auth_members_grantor - 1] = true;new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc, new_record, new_record_nulls, new_record_repl);simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple);CatalogUpdateIndexes(pg_authmem_rel, tuple);ReleaseSysCache(authmem_tuple);} else {tuple = heap_form_tuple(pg_authmem_dsc, new_record, new_record_nulls);(void)simple_heap_insert(pg_authmem_rel, tuple);CatalogUpdateIndexes(pg_authmem_rel, tuple);}}
. . .heap_close(pg_authmem_rel, NoLock);
}

函数DelRoleMems的实现过程类似。首先对执行者的相关权限进行校验,然后循环处理要删除的角色,删除系统表pg_auth_member中相关的元组。

感谢大家学习第9章 安全管理源码解析中“9.3 角色管理”的精彩内容,下一篇我们开启“9.4 对象权限管理”的相关内容的介绍。
敬请期待。

⭐openGauss数据库源码解析系列文章—— 角色管理⭐相关推荐

  1. openGauss数据库源码解析系列文章--openGauss简介(一)

    openGauss数据库是华为深度融合在数据库领域多年经验,结合企业级场景要求推出的新一代企业级开源数据库.此前,Gauss松鼠会已经发布了openGauss数据库核心技术系列文章,介绍了openGa ...

  2. openGauss数据库源码解析系列文章——openGauss开发快速入门(二)

    在上一篇openGauss数据库源码解析系列文章--openGauss开发快速入门(上)中,我们介绍了openGauss的安装部署方法,本篇将具体介绍openGauss基本使用. 二. openGau ...

  3. openGauss数据库源码解析系列文章—— AI技术之“自调优”

    上一篇介绍了第七章执行器解析中"7.6 向量化引擎"及"7.7 小结"的相关内容,本篇我们开启第八章 AI技术中"8.1 概述"及" ...

  4. ⭐openGauss数据库源码解析系列文章—— 对象权限管理⭐

    在前面文章中介绍过"9.3 角色管理整",本篇我们介绍第9章 安全管理源码解析中"9.4 对象权限管理"的相关精彩内容介绍. 9.4 对象权限管理 权限管理是安 ...

  5. openGauss数据库源码解析系列文章——openGauss开发快速入门(一)

    作为openGauss数据库开发者,在基于开源社区的openGauss版本进行二次开发的过程中,需要完成软件包获取.源码了解.代码修改.编译发布等过程,同时还需要安装数据库以了解数据库的基本特点.验证 ...

  6. openGauss数据库源码解析系列文章—— SQL引擎源解析(一)

    本篇我们开启"SQL引擎源解析"中"6.1 概述"及"6.2 SQL解析"的精彩内容介绍. 第6章 SQL引擎源解析 SQL引擎作为数据库系 ...

  7. openGauss数据库源码解析系列文章——SQL引擎源码解析(一)

    SQL引擎作为数据库系统的入口 , 主要承担了对SQL语言进行解析 . 优化 . 生成执行计划的 作用.对于用户输入的SQL语句,SQL引擎会对语句进行语法/ 语义上的分析以 判断是否满足语法规则等, ...

  8. openGauss数据库源码解析系列文章——存储引擎源码解析(四)

    上一篇我们详细讲述"3. astore元组多版本机制"."4.astore访存管理"及"5.astore空间管理和回收"相关内容.本篇我们将 ...

  9. prometheus变量_TiKV 源码解析系列文章(四)Prometheus(下)

    本文为 TiKV 源码解析系列的第四篇,接上篇继续为大家介绍 rust-prometheus.上篇主要介绍了基础知识以及最基本的几个指标的内部工作机制,本篇会进一步介绍更多高级功能的实现原理. 与上篇 ...

最新文章

  1. wordpress wpdb-update 能获取更新的id嘛?_WordPress 投稿页上传图片,支持游客上传
  2. 数据中心外包面临法律考验
  3. wsl2设置挂载_Windows下的Linux子系统安装,WSL 2下配置docker
  4. 老干妈如今做到这么大,为什么她就是没遇到竞敌?
  5. c++ 动态分配数组_C/C++编程笔记:「C语言指针」民间解读版本
  6. aop框架 php,xaop: 支持三种模式的AOP框架,弥补PHPer的不足,并且自带了文档的解析类库,可以一并使用,性能极好,欢迎 STAR 与 FORK。...
  7. tensorflow初试:mnist全连接分类
  8. 分布式数据库DDM Sidecar模式负载均衡
  9. cf. Lengthening Sticks 组合数学
  10. 【openwrt】使用4G模块 移远EC20/25(1)内核配置
  11. PHP表单省市县三级联动,用php做省份的三级联动 附带数据库
  12. 《the django book》part2 django的安装使用
  13. mellanox在vmware中的切割
  14. php7实践指南-ch15MySQL数据库的使用
  15. 程序员掉入传销组织用“代码”求救,同事秒懂
  16. 博瑞智能云音箱云喇叭API开发定时播报文档(2023-4-5)
  17. 十个免费的Web负载/压力测试工具
  18. 超市微信小程序怎么做_便利店超市运营微信小程序的三大技巧
  19. Line披露母公司Naver详情:去年营收28.4亿美元
  20. 微波射频学习笔记10-------T型结功率分配器

热门文章

  1. VScode格式化HTML代码保持标签属性不换行
  2. 有道云笔记修改护眼绿背景色-v6.8
  3. 分析腾讯微博登录过程
  4. How to install Bromine3 RC2
  5. 关于 hystrix 的异常 fallback method wasn't found
  6. 国内主流云服务器性能评测报告
  7. 奥哲低代码为新能源国企赋能,从0到1开启数字化新格局
  8. JS实现鼠标滑过图片的抖动效果
  9. [转]OKapi BM25 算法
  10. 向日葵如何远程显示全屏?