From c4d37819077077b11004893f08dd270209bff79d Mon Sep 17 00:00:00 2001
From: Egor Ivkov <e.o.ivkov@gmail.com>
Date: Tue, 21 Nov 2023 16:40:42 +0300
Subject: [PATCH] refactor: use object_id instead of object_name in privilege

---
 src/luamod.lua          |  28 +++++++----
 src/schema.rs           |  49 ++++++++++++------
 src/storage.rs          | 108 ++++++++++++++++++++--------------------
 src/traft/op.rs         |   8 +--
 test/int/test_acl.py    |  39 ++++++++++++---
 test/int/test_basics.py |  48 +++++++++---------
 6 files changed, 165 insertions(+), 115 deletions(-)

diff --git a/src/luamod.lua b/src/luamod.lua
index a124a4e038..84738fac3c 100644
--- a/src/luamod.lua
+++ b/src/luamod.lua
@@ -844,9 +844,12 @@ function pico.grant_privilege(grantee, privilege, object_type, object_name, opts
         end
 
         -- Throws error if object doesn't exist
-        object_resolve(object_type, object_name)
+        local object_id = object_resolve(object_type, object_name)
+        if object_id == "" then
+            object_id = -1
+        end
 
-        if box.space._pico_privilege:get{grantee_def.id, object_type, object_name, privilege} ~= nil then
+        if box.space._pico_privilege:get{grantee_def.id, object_type, object_id, privilege} ~= nil then
             -- Privilege is already granted, no op needed
             return nil
         end
@@ -855,11 +858,11 @@ function pico.grant_privilege(grantee, privilege, object_type, object_name, opts
             kind = 'acl',
             op_kind = 'grant_privilege',
             priv_def = {
-                grantor_id = box.session.uid(),
-                grantee_id = grantee_def.id,
-                object_type = object_type,
-                object_name = object_name,
                 privilege = privilege,
+                object_type = object_type,
+                object_id = object_id,
+                grantee_id = grantee_def.id,
+                grantor_id = box.session.uid(),
                 schema_version = next_schema_version(),
             },
         }
@@ -945,9 +948,12 @@ function pico.revoke_privilege(grantee, privilege, object_type, object_name, opt
         end
 
         -- Throws error if object doesn't exist
-        object_resolve(object_type, object_name)
+        local object_id = object_resolve(object_type, object_name)
+        if object_id == "" then
+            object_id = -1
+        end
 
-        local priv = box.space._pico_privilege:get{grantee_def.id, object_type, object_name, privilege}
+        local priv = box.space._pico_privilege:get{grantee_def.id, object_type, object_id, privilege}
         if priv == nil then
             -- Privilege is not yet granted, no op needed
             return nil
@@ -957,11 +963,11 @@ function pico.revoke_privilege(grantee, privilege, object_type, object_name, opt
             kind = 'acl',
             op_kind = 'revoke_privilege',
             priv_def = {
+                privilege = privilege,
+                object_type = object_type,
+                object_id = object_id,
                 grantee_id = grantee_def.id,
                 grantor_id = priv.grantor_id,
-                object_type = object_type,
-                object_name = object_name,
-                privilege = privilege,
                 schema_version = next_schema_version(),
             },
         }
diff --git a/src/schema.rs b/src/schema.rs
index 674acf4f71..59a433ab22 100644
--- a/src/schema.rs
+++ b/src/schema.rs
@@ -24,7 +24,7 @@ use tarantool::{
 
 use serde::{Deserialize, Serialize};
 
-use crate::bootstrap_entries::{ADMIN_ID, GUEST_ID, SUPER_ID};
+use crate::bootstrap_entries::{ADMIN_ID, GUEST_ID, PUBLIC_ID, SUPER_ID};
 use crate::cas::{self, compare_and_swap};
 use crate::storage::SPACE_ID_INTERNAL_MAX;
 use crate::storage::{ClusterwideTable, PropertyName};
@@ -330,15 +330,22 @@ impl RoleDef {
 /// Privilege definition.
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
 pub struct PrivilegeDef {
+    pub privilege: String,
+    pub object_type: String,
+    /// `-1` denotes an absense of a target object.
+    /// Other values should be >= 0 and denote an existing target object.
+    /// When working with `object_type` `universe` it might seem that it does
+    /// not have a target object and `object_id` should be `-1`, this is incorrect
+    /// universe has a target object with `object_id == 0`.
+    ///
+    /// To get the value of this field as `Option<u32>` see [`Self::object_id`]
+    pub object_id: i64,
     /// Id of the user or role to whom the privilege is granted.
     ///
     /// In tarantool users and roles are stored in the same space, which means a
     /// role and a user cannot have the same id or name.
-    pub grantor_id: UserId,
     pub grantee_id: UserId,
-    pub object_type: String,
-    pub object_name: String,
-    pub privilege: String,
+    pub grantor_id: UserId,
     pub schema_version: u64,
 }
 
@@ -350,11 +357,11 @@ impl PrivilegeDef {
     pub fn format() -> Vec<tarantool::space::Field> {
         use tarantool::space::Field;
         vec![
-            Field::from(("grantor_id", FieldType::Unsigned)),
-            Field::from(("grantee_id", FieldType::Unsigned)),
-            Field::from(("object_type", FieldType::String)),
-            Field::from(("object_name", FieldType::String)),
             Field::from(("privilege", FieldType::String)),
+            Field::from(("object_type", FieldType::String)),
+            Field::from(("object_id", FieldType::Integer)),
+            Field::from(("grantee_id", FieldType::Unsigned)),
+            Field::from(("grantor_id", FieldType::Unsigned)),
             Field::from(("schema_version", FieldType::Unsigned)),
         ]
     }
@@ -366,12 +373,23 @@ impl PrivilegeDef {
             grantor_id: 13,
             grantee_id: 37,
             object_type: "fruit".into(),
-            object_name: "banana".into(),
+            object_id: -1,
             privilege: "bite".into(),
             schema_version: 337,
         }
     }
 
+    /// Get `object_id` field interpreting `-1` as `None`.
+    #[inline(always)]
+    pub fn object_id(&self) -> Option<u32> {
+        if self.object_id >= 0 {
+            Some(self.object_id as _)
+        } else {
+            debug_assert_eq!(self.object_id, -1, "object_id should be >= -1");
+            None
+        }
+    }
+
     pub fn get_default_privileges() -> &'static Vec<PrivilegeDef> {
         static DEFAULT_PRIVILEGES: OnceCell<Vec<PrivilegeDef>> = OnceCell::new();
         DEFAULT_PRIVILEGES.get_or_init(|| {
@@ -383,7 +401,8 @@ impl PrivilegeDef {
                     grantor_id: ADMIN_ID,
                     grantee_id: GUEST_ID,
                     object_type: String::from("universe"),
-                    object_name: String::from(""),
+                    // `universe` has object_id 0
+                    object_id: 0,
                     privilege: String::from(privilege),
                     schema_version: 0,
                 });
@@ -392,12 +411,12 @@ impl PrivilegeDef {
             // execute public
             // execute on super (temporary until we switch to service account)
             // SQL: GRANT 'execute' ON <'public', 'user'> TO 'guest'
-            for role in ["public", "super"] {
+            for role in [PUBLIC_ID, SUPER_ID] {
                 v.push(PrivilegeDef {
                     grantor_id: ADMIN_ID,
                     grantee_id: GUEST_ID,
                     object_type: String::from("role"),
-                    object_name: String::from(role),
+                    object_id: role as _,
                     privilege: String::from("execute"),
                     schema_version: 0,
                 });
@@ -425,7 +444,7 @@ impl PrivilegeDef {
                     grantor_id: ADMIN_ID,
                     grantee_id: ADMIN_ID,
                     object_type: String::from("universe"),
-                    object_name: String::from(""),
+                    object_id: 0,
                     privilege: String::from(privilege),
                     schema_version: 0,
                 });
@@ -438,7 +457,7 @@ impl PrivilegeDef {
                     grantor_id: ADMIN_ID,
                     grantee_id: SUPER_ID,
                     object_type: String::from("universe"),
-                    object_name: String::from(""),
+                    object_id: 0,
                     privilege: String::from(privilege),
                     schema_version: 0,
                 });
diff --git a/src/storage.rs b/src/storage.rs
index 26c738937a..0db1949dfb 100644
--- a/src/storage.rs
+++ b/src/storage.rs
@@ -1905,11 +1905,9 @@ pub fn ddl_meta_space_update_operable(
 ///
 /// This function is called when applying the different ddl operations.
 pub fn ddl_meta_drop_space(storage: &Clusterwide, space_id: SpaceId) -> traft::Result<()> {
-    if let Some(space_name) = storage.tables.get(space_id)?.map(|s| s.name) {
-        storage
-            .privileges
-            .delete_all_by_object("space", &space_name)?;
-    }
+    storage
+        .privileges
+        .delete_all_by_object("space", space_id as i64)?;
     let iter = storage.indexes.by_space_id(space_id)?;
     for index in iter {
         storage.indexes.delete(index.table_id, index.id)?;
@@ -2263,7 +2261,7 @@ impl Privileges {
         let primary_key = space
             .index_builder("primary")
             .unique(true)
-            .parts(["grantee_id", "object_type", "object_name", "privilege"])
+            .parts(["grantee_id", "object_type", "object_id", "privilege"])
             .if_not_exists(true)
             .create()?;
 
@@ -2271,7 +2269,7 @@ impl Privileges {
             .index_builder("object")
             .unique(false)
             .part("object_type")
-            .part("object_name")
+            .part("object_id")
             .if_not_exists(true)
             .create()?;
 
@@ -2287,9 +2285,9 @@ impl Privileges {
         &self,
         grantee_id: UserId,
         object_type: &str,
-        object_name: &str,
+        object_id: i64,
     ) -> tarantool::Result<Option<PrivilegeDef>> {
-        let tuple = self.space.get(&(grantee_id, object_type, object_name))?;
+        let tuple = self.space.get(&(grantee_id, object_type, object_id))?;
         tuple.as_ref().map(Tuple::decode).transpose()
     }
 
@@ -2303,11 +2301,11 @@ impl Privileges {
     pub fn by_object(
         &self,
         object_type: &str,
-        object_name: &str,
+        object_id: i64,
     ) -> tarantool::Result<EntryIter<PrivilegeDef>> {
         let iter = self
             .object_idx
-            .select(IteratorType::Eq, &(object_type, object_name))?;
+            .select(IteratorType::Eq, &(object_type, object_id))?;
         Ok(EntryIter::new(iter))
     }
 
@@ -2328,11 +2326,11 @@ impl Privileges {
         &self,
         grantee_id: UserId,
         object_type: &str,
-        object_name: &str,
+        object_id: i64,
         privilege: &str,
     ) -> tarantool::Result<()> {
         self.space
-            .delete(&(grantee_id, object_type, object_name, privilege))?;
+            .delete(&(grantee_id, object_type, object_id, privilege))?;
         Ok(())
     }
 
@@ -2343,7 +2341,7 @@ impl Privileges {
             self.delete(
                 priv_def.grantee_id,
                 &priv_def.object_type,
-                &priv_def.object_name,
+                priv_def.object_id,
                 &priv_def.privilege,
             )?;
         }
@@ -2352,16 +2350,16 @@ impl Privileges {
 
     /// Remove any privilege definitions assigning the given role.
     #[inline]
-    pub fn delete_all_by_granted_role(&self, role_name: &str) -> Result<()> {
+    pub fn delete_all_by_granted_role(&self, role_id: u32) -> Result<()> {
         for priv_def in self.iter()? {
             if priv_def.privilege == "execute"
                 && priv_def.object_type == "role"
-                && priv_def.object_name == role_name
+                && priv_def.object_id == role_id as i64
             {
                 self.delete(
                     priv_def.grantee_id,
                     &priv_def.object_type,
-                    &priv_def.object_name,
+                    priv_def.object_id,
                     &priv_def.privilege,
                 )?;
             }
@@ -2371,16 +2369,12 @@ impl Privileges {
 
     /// Remove any privilege definitions granted to the given object.
     #[inline]
-    pub fn delete_all_by_object(
-        &self,
-        object_type: &str,
-        object_name: &str,
-    ) -> tarantool::Result<()> {
-        for priv_def in self.by_object(object_type, object_name)? {
+    pub fn delete_all_by_object(&self, object_type: &str, object_id: i64) -> tarantool::Result<()> {
+        for priv_def in self.by_object(object_type, object_id)? {
             self.delete(
                 priv_def.grantee_id,
                 &priv_def.object_type,
-                &priv_def.object_name,
+                priv_def.object_id,
                 &priv_def.privilege,
             )?;
         }
@@ -2732,24 +2726,18 @@ pub mod acl {
     /// Remove a role definition and any entities owned by it from the internal
     /// clusterwide storage.
     pub fn global_drop_role(storage: &Clusterwide, role_id: UserId) -> Result<()> {
+        let role_def = storage.roles.by_id(role_id)?.expect("role should exist");
         storage.privileges.delete_all_by_grantee_id(role_id)?;
+        storage.privileges.delete_all_by_granted_role(role_id)?;
+        storage.roles.delete(role_id)?;
 
-        if let Some(role_def) = storage.roles.by_id(role_id)? {
-            // Revoke the role from any grantees.
-            storage
-                .privileges
-                .delete_all_by_granted_role(&role_def.name)?;
-            storage.roles.delete(role_id)?;
-
-            let role = &role_def.name;
-            crate::audit!(
-                message: "dropped role `{role}`",
-                title: "drop_role",
-                severity: Medium,
-                role: role,
-            );
-        }
-
+        let role = &role_def.name;
+        crate::audit!(
+            message: "dropped role `{role}`",
+            title: "drop_role",
+            severity: Medium,
+            role: role,
+        );
         Ok(())
     }
 
@@ -2761,11 +2749,12 @@ pub mod acl {
         storage.privileges.insert(priv_def)?;
 
         let privilege = &priv_def.privilege;
-        let (object, object_type) = (&priv_def.object_name, &priv_def.object_type);
+        let (object, object_type) = (priv_def.object_id(), &priv_def.object_type);
         let (grantee_type, grantee) = priv_def.grantee_type_and_name(storage)?;
 
         match (privilege.as_str(), object_type.as_str()) {
             ("execute", "role") => {
+                let object = object.expect("should be set");
                 crate::audit!(
                     message: "granted role `{object}` to {grantee_type} `{grantee}`",
                     title: "grant_role",
@@ -2776,13 +2765,17 @@ pub mod acl {
                 );
             }
             _ => {
+                let object = match object {
+                    Some(object) => format!("`{object}` "),
+                    None => "".into(),
+                };
                 crate::audit!(
-                    message: "granted privilege {privilege} on {object_type} `{object}` \
+                    message: "granted privilege {privilege} on {object_type} {object}\
                               to {grantee_type} `{grantee}`",
                     title: "grant_privilege",
                     severity: High,
                     privilege: privilege,
-                    object: object,
+                    object: priv_def.object_id(),
                     object_type: object_type,
                     grantee: &grantee,
                     grantee_type: grantee_type,
@@ -2802,16 +2795,17 @@ pub mod acl {
         storage.privileges.delete(
             priv_def.grantee_id,
             &priv_def.object_type,
-            &priv_def.object_name,
+            priv_def.object_id,
             &priv_def.privilege,
         )?;
 
         let privilege = &priv_def.privilege;
-        let (object, object_type) = (&priv_def.object_name, &priv_def.object_type);
+        let (object, object_type) = (priv_def.object_id(), &priv_def.object_type);
         let (grantee_type, grantee) = priv_def.grantee_type_and_name(storage)?;
 
         match (privilege.as_str(), object_type.as_str()) {
             ("execute", "role") => {
+                let object = object.expect("should be set");
                 crate::audit!(
                     message: "revoke role `{object}` from {grantee_type} `{grantee}`",
                     title: "revoke_role",
@@ -2822,13 +2816,17 @@ pub mod acl {
                 );
             }
             _ => {
+                let object = match object {
+                    Some(object) => format!("`{object}` "),
+                    None => "".into(),
+                };
                 crate::audit!(
-                    message: "revoked privilege {privilege} on {object_type} `{object}` \
+                    message: "revoked privilege {privilege} on {object_type} {object}\
                               from {grantee_type} `{grantee}`",
                     title: "revoke_privilege",
                     severity: High,
                     privilege: privilege,
-                    object: object,
+                    object: priv_def.object_id(),
                     object_type: object_type,
                     grantee: &grantee,
                     grantee_type: grantee_type,
@@ -2939,18 +2937,18 @@ pub mod acl {
     pub fn on_master_grant_privilege(priv_def: &PrivilegeDef) -> tarantool::Result<()> {
         let lua = ::tarantool::lua_state();
         lua.exec_with(
-            "local grantee_id, privilege, object_type, object_name = ...
+            "local grantee_id, privilege, object_type, object_id = ...
             local grantee_def = box.space._user:get(grantee_id)
             if grantee_def.type == 'user' then
-                box.schema.user.grant(grantee_id, privilege, object_type, object_name)
+                box.schema.user.grant(grantee_id, privilege, object_type, object_id)
             else
-                box.schema.role.grant(grantee_id, privilege, object_type, object_name)
+                box.schema.role.grant(grantee_id, privilege, object_type, object_id)
             end",
             (
                 priv_def.grantee_id,
                 &priv_def.privilege,
                 &priv_def.object_type,
-                &priv_def.object_name,
+                priv_def.object_id(),
             ),
         )
         .map_err(LuaError::from)?;
@@ -2962,22 +2960,22 @@ pub mod acl {
     pub fn on_master_revoke_privilege(priv_def: &PrivilegeDef) -> tarantool::Result<()> {
         let lua = ::tarantool::lua_state();
         lua.exec_with(
-            "local grantee_id, privilege, object_type, object_name = ...
+            "local grantee_id, privilege, object_type, object_id = ...
             local grantee_def = box.space._user:get(grantee_id)
             if not grantee_def then
                 -- Grantee already dropped -> privileges already revoked
                 return
             end
             if grantee_def.type == 'user' then
-                box.schema.user.revoke(grantee_id, privilege, object_type, object_name)
+                box.schema.user.revoke(grantee_id, privilege, object_type, object_id)
             else
-                box.schema.role.revoke(grantee_id, privilege, object_type, object_name)
+                box.schema.role.revoke(grantee_id, privilege, object_type, object_id)
             end",
             (
                 priv_def.grantee_id,
                 &priv_def.privilege,
                 &priv_def.object_type,
-                &priv_def.object_name,
+                priv_def.object_id(),
             ),
         )
         .map_err(LuaError::from)?;
diff --git a/src/traft/op.rs b/src/traft/op.rs
index 062ed4e1d3..6472996f29 100644
--- a/src/traft/op.rs
+++ b/src/traft/op.rs
@@ -152,28 +152,28 @@ impl std::fmt::Display for Op {
                 write!(f, "DropRole({schema_version}, {role_id})")
             }
             Self::Acl(Acl::GrantPrivilege { priv_def }) => {
+                let object_id = priv_def.object_id();
                 let PrivilegeDef {
                     grantee_id,
                     grantor_id,
                     object_type,
-                    object_name,
                     privilege,
                     schema_version,
                     ..
                 } = priv_def;
-                write!(f, "GrantPrivilege({schema_version}, {grantor_id}, {grantee_id}, {object_type}, {object_name}, {privilege})")
+                write!(f, "GrantPrivilege({schema_version}, {grantor_id}, {grantee_id}, {object_type}, {object_id:?}, {privilege})")
             }
             Self::Acl(Acl::RevokePrivilege { priv_def }) => {
+                let object_id = priv_def.object_id();
                 let PrivilegeDef {
                     grantee_id,
                     grantor_id,
                     object_type,
-                    object_name,
                     privilege,
                     schema_version,
                     ..
                 } = priv_def;
-                write!(f, "RevokePrivilege({schema_version}, {grantor_id}, {grantee_id}, {object_type}, {object_name}, {privilege})")
+                write!(f, "RevokePrivilege({schema_version}, {grantor_id}, {grantee_id}, {object_type}, {object_id:?}, {privilege})")
             }
         };
 
diff --git a/test/int/test_acl.py b/test/int/test_acl.py
index 4ab169a52b..aee5303c66 100644
--- a/test/int/test_acl.py
+++ b/test/int/test_acl.py
@@ -252,11 +252,30 @@ def test_acl_lua_api(cluster: Cluster):
 
     dave_id = i1.call("box.space._pico_user.index.name:get", "Dave")[0]
 
+    pico_property_id = i1.eval("return box.space._pico_property.id")
     priv = i1.call(
-        "box.space._pico_privilege:get", (dave_id, "space", "_pico_property", "read")
+        "box.space._pico_privilege:get",
+        (dave_id, "space", pico_property_id, "read"),
     )
 
-    assert priv[0] == 0  # The above grant was executed from guest. 0 is guest user id.
+    # grantor_id is the field at index 4 in schema::PrivilegeDef
+    assert priv[4] == 0  # The above grant was executed from guest. 0 is guest user id.
+
+    # Grant privilege to user without object_name -> Ok.
+    i1.call(
+        "pico.grant_privilege",
+        "Dave",
+        "create",
+        "user",
+    )
+
+    priv = i1.call(
+        "box.space._pico_privilege:get",
+        (dave_id, "user", -1, "create"),
+    )
+
+    # grantor_id is the field at index 4 in schema::PrivilegeDef
+    assert priv[4] == 0  # The above grant was executed from guest. 0 is guest user id.
 
     # Already granted -> ok.
     i1.call(
@@ -295,7 +314,7 @@ def test_acl_lua_api(cluster: Cluster):
     # pico.revoke_privilege semantics verification
     #
 
-    # Revoke privilege to user -> Ok.
+    # Revoke privilege from user -> Ok.
     i1.call(
         "pico.revoke_privilege",
         "Dave",
@@ -304,6 +323,14 @@ def test_acl_lua_api(cluster: Cluster):
         "_pico_property",
     )
 
+    # Revoke privilege from user without object_name -> Ok.
+    i1.call(
+        "pico.revoke_privilege",
+        "Dave",
+        "create",
+        "user",
+    )
+
     # Already revoked -> ok.
     i1.call(
         "pico.revoke_privilege",
@@ -313,7 +340,7 @@ def test_acl_lua_api(cluster: Cluster):
         "_pico_property",
     )
 
-    # Revoke privilege to role -> Ok.
+    # Revoke privilege from role -> Ok.
     i1.call(
         "pico.revoke_privilege",
         "Parent",
@@ -331,7 +358,7 @@ def test_acl_lua_api(cluster: Cluster):
         "_pico_property",
     )
 
-    # Revoke role to user -> Ok.
+    # Revoke role from user -> Ok.
     i1.call("pico.revoke_privilege", "Dave", "execute", "role", "Parent")
 
     # Already revoked role to user -> ok.
@@ -862,6 +889,7 @@ def test_builtin_users_and_roles(cluster: Cluster):
             "_pico_property",
         )
 
+    # granting already granted privilege does not raise an error
     index = i1.call("pico.raft_get_index")
     new_index = i1.call(
         "pico.grant_privilege",
@@ -869,7 +897,6 @@ def test_builtin_users_and_roles(cluster: Cluster):
         "write",
         "universe",
     )
-
     assert index == new_index
 
     index = i1.call("pico.create_user", "Dave", VALID_PASSWORD)
diff --git a/test/int/test_basics.py b/test/int/test_basics.py
index 8f311c536c..d84c7bb7f9 100644
--- a/test/int/test_basics.py
+++ b/test/int/test_basics.py
@@ -255,30 +255,30 @@ def test_raft_log(instance: Instance):
 | 13  | 1  |1.0.13|Insert({_pico_user}, [1,"admin",0,["chap-sha1",""]])|
 | 14  | 1  |1.0.14|Insert({_pico_role}, [2,"public",0])|
 | 15  | 1  |1.0.15|Insert({_pico_role}, [31,"super",0])|
-| 16  | 1  |1.0.16|Insert({_pico_privilege}, [1,0,"universe","","usage",0])|
-| 17  | 1  |1.0.17|Insert({_pico_privilege}, [1,0,"universe","","session",0])|
-| 18  | 1  |1.0.18|Insert({_pico_privilege}, [1,0,"role","public","execute",0])|
-| 19  | 1  |1.0.19|Insert({_pico_privilege}, [1,0,"role","super","execute",0])|
-| 20  | 1  |1.0.20|Insert({_pico_privilege}, [1,1,"universe","","read",0])|
-| 21  | 1  |1.0.21|Insert({_pico_privilege}, [1,1,"universe","","write",0])|
-| 22  | 1  |1.0.22|Insert({_pico_privilege}, [1,1,"universe","","execute",0])|
-| 23  | 1  |1.0.23|Insert({_pico_privilege}, [1,1,"universe","","session",0])|
-| 24  | 1  |1.0.24|Insert({_pico_privilege}, [1,1,"universe","","usage",0])|
-| 25  | 1  |1.0.25|Insert({_pico_privilege}, [1,1,"universe","","create",0])|
-| 26  | 1  |1.0.26|Insert({_pico_privilege}, [1,1,"universe","","drop",0])|
-| 27  | 1  |1.0.27|Insert({_pico_privilege}, [1,1,"universe","","alter",0])|
-| 28  | 1  |1.0.28|Insert({_pico_privilege}, [1,1,"universe","","grant",0])|
-| 29  | 1  |1.0.29|Insert({_pico_privilege}, [1,1,"universe","","revoke",0])|
-| 30  | 1  |1.0.30|Insert({_pico_privilege}, [1,31,"universe","","read",0])|
-| 31  | 1  |1.0.31|Insert({_pico_privilege}, [1,31,"universe","","write",0])|
-| 32  | 1  |1.0.32|Insert({_pico_privilege}, [1,31,"universe","","execute",0])|
-| 33  | 1  |1.0.33|Insert({_pico_privilege}, [1,31,"universe","","session",0])|
-| 34  | 1  |1.0.34|Insert({_pico_privilege}, [1,31,"universe","","usage",0])|
-| 35  | 1  |1.0.35|Insert({_pico_privilege}, [1,31,"universe","","create",0])|
-| 36  | 1  |1.0.36|Insert({_pico_privilege}, [1,31,"universe","","drop",0])|
-| 37  | 1  |1.0.37|Insert({_pico_privilege}, [1,31,"universe","","alter",0])|
-| 38  | 1  |1.0.38|Insert({_pico_privilege}, [1,31,"universe","","grant",0])|
-| 39  | 1  |1.0.39|Insert({_pico_privilege}, [1,31,"universe","","revoke",0])|
+| 16  | 1  |1.0.16|Insert({_pico_privilege}, ["usage","universe",0,0,1,0])|
+| 17  | 1  |1.0.17|Insert({_pico_privilege}, ["session","universe",0,0,1,0])|
+| 18  | 1  |1.0.18|Insert({_pico_privilege}, ["execute","role",2,0,1,0])|
+| 19  | 1  |1.0.19|Insert({_pico_privilege}, ["execute","role",31,0,1,0])|
+| 20  | 1  |1.0.20|Insert({_pico_privilege}, ["read","universe",0,1,1,0])|
+| 21  | 1  |1.0.21|Insert({_pico_privilege}, ["write","universe",0,1,1,0])|
+| 22  | 1  |1.0.22|Insert({_pico_privilege}, ["execute","universe",0,1,1,0])|
+| 23  | 1  |1.0.23|Insert({_pico_privilege}, ["session","universe",0,1,1,0])|
+| 24  | 1  |1.0.24|Insert({_pico_privilege}, ["usage","universe",0,1,1,0])|
+| 25  | 1  |1.0.25|Insert({_pico_privilege}, ["create","universe",0,1,1,0])|
+| 26  | 1  |1.0.26|Insert({_pico_privilege}, ["drop","universe",0,1,1,0])|
+| 27  | 1  |1.0.27|Insert({_pico_privilege}, ["alter","universe",0,1,1,0])|
+| 28  | 1  |1.0.28|Insert({_pico_privilege}, ["grant","universe",0,1,1,0])|
+| 29  | 1  |1.0.29|Insert({_pico_privilege}, ["revoke","universe",0,1,1,0])|
+| 30  | 1  |1.0.30|Insert({_pico_privilege}, ["read","universe",0,31,1,0])|
+| 31  | 1  |1.0.31|Insert({_pico_privilege}, ["write","universe",0,31,1,0])|
+| 32  | 1  |1.0.32|Insert({_pico_privilege}, ["execute","universe",0,31,1,0])|
+| 33  | 1  |1.0.33|Insert({_pico_privilege}, ["session","universe",0,31,1,0])|
+| 34  | 1  |1.0.34|Insert({_pico_privilege}, ["usage","universe",0,31,1,0])|
+| 35  | 1  |1.0.35|Insert({_pico_privilege}, ["create","universe",0,31,1,0])|
+| 36  | 1  |1.0.36|Insert({_pico_privilege}, ["drop","universe",0,31,1,0])|
+| 37  | 1  |1.0.37|Insert({_pico_privilege}, ["alter","universe",0,31,1,0])|
+| 38  | 1  |1.0.38|Insert({_pico_privilege}, ["grant","universe",0,31,1,0])|
+| 39  | 1  |1.0.39|Insert({_pico_privilege}, ["revoke","universe",0,31,1,0])|
 | 40  | 1  |     |AddNode(1)|
 | 41  | 2  |     |-|
 | 42  | 2  |1.1.1|Replace({_pico_instance}, ["i1","68d4a766-4144-3248-aeb4-e212356716e4",1,"r1","e0df68c5-e7f9-395f-86b3-30ad9e1b7b07",["Offline",0],["Online",1],{b},"default"])|
-- 
GitLab