From 53dbcc699bffaacdd695945959067cc9d0603bef Mon Sep 17 00:00:00 2001
From: Dmitry Rodionov <d.rodionov@picodata.io>
Date: Wed, 29 Nov 2023 20:03:28 +0300
Subject: [PATCH] refactor: make fields of PrivilegeDef private

This allows future constructors to enforce invariants.
Additionally this commit removes redundant set_schema_version on Op by
passing schema version to corresponding Op constructors.
---
 src/access_control.rs |  35 +++++------
 src/schema.rs         |  82 ++++++++++++++++++++------
 src/sql.rs            |  64 +++++++++-----------
 src/storage.rs        | 133 ++++++++++++++++++++++--------------------
 src/traft/op.rs       |  65 +++++++--------------
 5 files changed, 201 insertions(+), 178 deletions(-)

diff --git a/src/access_control.rs b/src/access_control.rs
index 0600f49fbc..374f89b131 100644
--- a/src/access_control.rs
+++ b/src/access_control.rs
@@ -226,7 +226,7 @@ fn access_check_grant_revoke(
             if grantor_id != ADMIN_USER_ID {
                 return Err(make_access_denied(
                     access_name,
-                    priv_def.object_type,
+                    priv_def.object_type(),
                     "",
                     grantor.name,
                 ));
@@ -236,7 +236,7 @@ fn access_check_grant_revoke(
         Some(object_id) => object_id,
     };
 
-    match priv_def.object_type {
+    match priv_def.object_type() {
         PicoSchemaObjectType::Universe => {
             // Only admin can grant on universe
             if grantor_id != ADMIN_USER_ID {
@@ -284,7 +284,8 @@ fn access_check_grant_revoke(
             // Note that having a role means having execute privilege on it.
             if sys_role_meta.owner_id != grantor_id
                 && grantor_id != ADMIN_USER_ID
-                && !(sys_role_meta.name == "public" && priv_def.privilege == PrivilegeType::Execute)
+                && !(sys_role_meta.name == "public"
+                    && priv_def.privilege() == PrivilegeType::Execute)
             {
                 return Err(make_access_denied(
                     access_name,
@@ -524,20 +525,20 @@ mod tests {
         grantee_id: UserId,
         grantor_id: Option<UserId>,
     ) {
-        let priv_def = PrivilegeDef {
-            grantor_id: grantor_id.unwrap_or(session::uid().unwrap()),
-            grantee_id,
+        let priv_def = PrivilegeDef::new(
+            privilege,
             object_type,
             object_id,
-            privilege,
-            schema_version: 0,
-        };
+            grantee_id,
+            grantor_id.unwrap_or(session::uid().unwrap()),
+            0,
+        );
 
         access_check_op(
             &Op::Acl(Acl::GrantPrivilege {
                 priv_def: priv_def.clone(),
             }),
-            priv_def.grantor_id,
+            priv_def.grantor_id(),
         )
         .unwrap();
 
@@ -551,20 +552,20 @@ mod tests {
         object_type: SchemaObjectType,
         object_id: i64,
     ) {
-        let priv_def = PrivilegeDef {
-            grantor_id: session::uid().unwrap(),
-            grantee_id,
+        let priv_def = PrivilegeDef::new(
+            privilege,
             object_type,
             object_id,
-            privilege,
-            schema_version: 0,
-        };
+            grantee_id,
+            session::uid().unwrap(),
+            0,
+        );
 
         access_check_op(
             &Op::Acl(Acl::RevokePrivilege {
                 priv_def: priv_def.clone(),
             }),
-            priv_def.grantor_id,
+            priv_def.grantor_id(),
         )
         .unwrap();
         on_master_revoke_privilege(&priv_def).unwrap()
diff --git a/src/schema.rs b/src/schema.rs
index 10d9cbfb98..a841dabed7 100644
--- a/src/schema.rs
+++ b/src/schema.rs
@@ -389,8 +389,8 @@ tarantool::define_str_enum! {
 /// Privilege definition.
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
 pub struct PrivilegeDef {
-    pub privilege: PrivilegeType,
-    pub object_type: SchemaObjectType,
+    privilege: PrivilegeType,
+    object_type: SchemaObjectType,
     /// `-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
@@ -398,19 +398,78 @@ pub struct PrivilegeDef {
     /// 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,
+    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 grantee_id: UserId,
-    pub grantor_id: UserId,
-    pub schema_version: u64,
+    grantee_id: UserId,
+    grantor_id: UserId,
+    schema_version: u64,
 }
 
 impl Encode for PrivilegeDef {}
 
 impl PrivilegeDef {
+    pub fn new(
+        privilege: PrivilegeType,
+        object_type: SchemaObjectType,
+        object_id: i64,
+        grantee_id: UserId,
+        grantor_id: UserId,
+        schema_version: u64,
+    ) -> PrivilegeDef {
+        PrivilegeDef {
+            privilege,
+            object_type,
+            object_id,
+            grantee_id,
+            grantor_id,
+            schema_version,
+        }
+    }
+
+    #[inline(always)]
+    pub fn privilege(&self) -> PrivilegeType {
+        self.privilege
+    }
+
+    #[inline(always)]
+    pub fn object_type(&self) -> SchemaObjectType {
+        self.object_type
+    }
+
+    /// 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
+        }
+    }
+
+    #[inline(always)]
+    pub fn object_id_raw(&self) -> i64 {
+        self.object_id
+    }
+
+    #[inline(always)]
+    pub fn grantee_id(&self) -> UserId {
+        self.grantee_id
+    }
+
+    #[inline(always)]
+    pub fn grantor_id(&self) -> UserId {
+        self.grantor_id
+    }
+
+    #[inline(always)]
+    pub fn schema_version(&self) -> u64 {
+        self.schema_version
+    }
+
     /// Format of the _pico_privilege global table.
     #[inline(always)]
     pub fn format() -> Vec<tarantool::space::Field> {
@@ -438,17 +497,6 @@ impl PrivilegeDef {
         }
     }
 
-    /// 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(|| {
diff --git a/src/sql.rs b/src/sql.rs
index 173cffeca0..407152b392 100644
--- a/src/sql.rs
+++ b/src/sql.rs
@@ -718,8 +718,10 @@ fn reenterable_schema_change_request(
             continue 'retry;
         }
 
+        let schema_version = storage.properties.next_schema_version()?;
+
         // Check for conflicts and make the op
-        let mut op = match &params {
+        let op = match &params {
             Params::CreateTable(params) => {
                 if params.space_exists()? {
                     // Space already exists, no op needed
@@ -736,8 +738,7 @@ fn reenterable_schema_change_request(
                 params.test_create_space(storage)?;
                 let ddl = params.into_ddl()?;
                 Op::DdlPrepare {
-                    // This field will be updated later.
-                    schema_version: 0,
+                    schema_version,
                     ddl,
                 }
             }
@@ -748,8 +749,7 @@ fn reenterable_schema_change_request(
                 };
                 let ddl = OpDdl::DropTable { id: space_def.id };
                 Op::DdlPrepare {
-                    // This field will be updated later.
-                    schema_version: 0,
+                    schema_version,
                     ddl,
                 }
             }
@@ -770,8 +770,7 @@ fn reenterable_schema_change_request(
                 let user_def = UserDef {
                     id,
                     name: name.clone(),
-                    // This field will be updated later.
-                    schema_version: 0,
+                    schema_version,
                     auth: auth.clone(),
                     owner: current_user,
                 };
@@ -794,15 +793,14 @@ fn reenterable_schema_change_request(
                 let object_type = SchemaObjectType::Universe;
                 let object_id = 0;
                 let privilege = PrivilegeType::Session;
-                let priv_def = PrivilegeDef {
-                    grantor_id,
-                    grantee_id,
+                let priv_def = PrivilegeDef::new(
+                    privilege,
                     object_type,
                     object_id,
-                    privilege,
-                    // This field will be updated later.
-                    schema_version: 0,
-                };
+                    grantee_id,
+                    grantor_id,
+                    schema_version,
+                );
 
                 match alter_option_param {
                     AlterOptionParam::ChangePassword(auth) => {
@@ -813,8 +811,7 @@ fn reenterable_schema_change_request(
                         Op::Acl(OpAcl::ChangeAuth {
                             user_id: user_def.id,
                             auth: auth.clone(),
-                            // This field will be updated later.
-                            schema_version: 0,
+                            schema_version,
                         })
                     }
 
@@ -853,8 +850,7 @@ fn reenterable_schema_change_request(
                 };
                 Op::Acl(OpAcl::DropUser {
                     user_id: user_def.id,
-                    // This field will be updated later.
-                    schema_version: 0,
+                    schema_version,
                 })
             }
             Params::CreateRole(name) => {
@@ -878,7 +874,7 @@ fn reenterable_schema_change_request(
                     id,
                     name: name.clone(),
                     // This field will be updated later.
-                    schema_version: 0,
+                    schema_version,
                     owner: current_user,
                 };
                 Op::Acl(OpAcl::CreateRole { role_def })
@@ -890,8 +886,7 @@ fn reenterable_schema_change_request(
                 };
                 Op::Acl(OpAcl::DropRole {
                     role_id: role_def.id,
-                    // This field will be updated later.
-                    schema_version: 0,
+                    schema_version,
                 })
             }
             Params::GrantPrivilege(grant_type, grantee_name) => {
@@ -910,15 +905,14 @@ fn reenterable_schema_change_request(
                     return Ok(ConsumerResult { row_count: 0 });
                 }
                 Op::Acl(OpAcl::GrantPrivilege {
-                    priv_def: PrivilegeDef {
-                        grantor_id,
-                        grantee_id,
+                    priv_def: PrivilegeDef::new(
+                        privilege,
                         object_type,
                         object_id,
-                        privilege,
-                        // This field will be updated later.
-                        schema_version: 0,
-                    },
+                        grantee_id,
+                        grantor_id,
+                        schema_version,
+                    ),
                 })
             }
             Params::RevokePrivilege(revoke_type, grantee_name) => {
@@ -938,19 +932,17 @@ fn reenterable_schema_change_request(
                 }
 
                 Op::Acl(OpAcl::RevokePrivilege {
-                    priv_def: PrivilegeDef {
-                        grantor_id,
-                        grantee_id,
+                    priv_def: PrivilegeDef::new(
+                        privilege,
                         object_type,
                         object_id,
-                        privilege,
-                        // This field will be updated later.
-                        schema_version: 0,
-                    },
+                        grantee_id,
+                        grantor_id,
+                        schema_version,
+                    ),
                 })
             }
         };
-        op.set_schema_version(storage.properties.next_schema_version()?);
         let is_ddl_prepare = matches!(op, Op::DdlPrepare { .. });
 
         let term = raft::Storage::term(&node.raft_storage, index)?;
diff --git a/src/storage.rs b/src/storage.rs
index a9d313b613..fb89dbade1 100644
--- a/src/storage.rs
+++ b/src/storage.rs
@@ -669,7 +669,7 @@ impl Clusterwide {
             old_role_versions.insert(def.id, def.schema_version);
         }
         for def in self.privileges.iter()? {
-            let schema_version = def.schema_version;
+            let schema_version = def.schema_version();
             old_priv_versions.insert(def, schema_version);
         }
 
@@ -2465,10 +2465,10 @@ impl Privileges {
     pub fn delete_all_by_grantee_id(&self, grantee_id: UserId) -> tarantool::Result<()> {
         for priv_def in self.by_grantee_id(grantee_id)? {
             self.delete(
-                priv_def.grantee_id,
-                &priv_def.object_type,
-                priv_def.object_id,
-                &priv_def.privilege,
+                priv_def.grantee_id(),
+                &priv_def.object_type(),
+                priv_def.object_id_raw(),
+                &priv_def.privilege(),
             )?;
         }
         Ok(())
@@ -2478,15 +2478,15 @@ impl Privileges {
     #[inline]
     pub fn delete_all_by_granted_role(&self, role_id: u32) -> Result<()> {
         for priv_def in self.iter()? {
-            if priv_def.privilege == PrivilegeType::Execute
-                && priv_def.object_type == SchemaObjectType::Role
-                && priv_def.object_id == role_id as i64
+            if priv_def.privilege() == PrivilegeType::Execute
+                && priv_def.object_type() == SchemaObjectType::Role
+                && priv_def.object_id() == Some(role_id)
             {
                 self.delete(
-                    priv_def.grantee_id,
-                    &priv_def.object_type,
-                    priv_def.object_id,
-                    &priv_def.privilege,
+                    priv_def.grantee_id(),
+                    &priv_def.object_type(),
+                    priv_def.object_id_raw(),
+                    &priv_def.privilege(),
                 )?;
             }
         }
@@ -2501,11 +2501,12 @@ impl Privileges {
         object_id: i64,
     ) -> tarantool::Result<()> {
         for priv_def in self.by_object(object_type, object_id)? {
+            debug_assert_eq!(priv_def.object_id_raw(), object_id);
             self.delete(
-                priv_def.grantee_id,
-                &priv_def.object_type,
-                priv_def.object_id,
-                &priv_def.privilege,
+                priv_def.grantee_id(),
+                &priv_def.object_type(),
+                object_id,
+                &priv_def.privilege(),
             )?;
         }
         Ok(())
@@ -2729,7 +2730,9 @@ impl SchemaDef for PrivilegeDef {
 
     #[inline(always)]
     fn schema_version(&self) -> u64 {
-        self.schema_version
+        // Note: this doesnt lead to recursion because of the
+        // method resolution order: https://doc.rust-lang.org/reference/expressions/method-call-expr.html
+        self.schema_version()
     }
 
     #[inline(always)]
@@ -2767,8 +2770,8 @@ pub mod acl {
             &self,
             storage: &Clusterwide,
         ) -> tarantool::Result<(&'static str, String)> {
-            let role_def = storage.roles.by_id(self.grantee_id)?;
-            let user_def = storage.users.by_id(self.grantee_id)?;
+            let role_def = storage.roles.by_id(self.grantee_id())?;
+            let user_def = storage.users.by_id(self.grantee_id())?;
             match (role_def, user_def) {
                 (Some(role_def), None) => Ok(("role", role_def.name)),
                 (None, Some(user_def)) => Ok(("user", user_def.name)),
@@ -2785,41 +2788,45 @@ pub mod acl {
     ) -> [PrivilegeDef; 4] {
         [
             // SQL: GRANT 'public' TO <user_name>
-            PrivilegeDef {
-                privilege: PrivilegeType::Execute,
-                object_type: SchemaObjectType::Role,
-                object_id: PUBLIC_ID as _,
-                grantee_id: user_def.id,
+            PrivilegeDef::new(
+                PrivilegeType::Execute,
+                SchemaObjectType::Role,
+                PUBLIC_ID as _,
+                user_def.id,
                 grantor_id,
-                schema_version: user_def.schema_version,
-            },
+                user_def.schema_version,
+            )
+            .expect("valid"),
             // ALTER USER <user_name> LOGIN
-            PrivilegeDef {
-                privilege: PrivilegeType::Session,
-                object_type: SchemaObjectType::Universe,
-                object_id: UNIVERSE_ID,
-                grantee_id: user_def.id,
-                grantor_id: ADMIN_ID,
-                schema_version: user_def.schema_version,
-            },
+            PrivilegeDef::new(
+                PrivilegeType::Session,
+                SchemaObjectType::Universe,
+                UNIVERSE_ID,
+                user_def.id,
+                ADMIN_ID,
+                user_def.schema_version,
+            )
+            .expect("valid"),
             // ALTER USER <user_name> ENABLE
-            PrivilegeDef {
-                privilege: PrivilegeType::Usage,
-                object_type: SchemaObjectType::Universe,
-                object_id: UNIVERSE_ID,
-                grantee_id: user_def.id,
-                grantor_id: ADMIN_ID,
-                schema_version: user_def.schema_version,
-            },
+            PrivilegeDef::new(
+                PrivilegeType::Usage,
+                SchemaObjectType::Universe,
+                UNIVERSE_ID,
+                user_def.id,
+                ADMIN_ID,
+                user_def.schema_version,
+            )
+            .expect("valid"),
             // SQL: GRANT ALTER ON <user_name> TO <user_name>
-            PrivilegeDef {
-                privilege: PrivilegeType::Alter,
-                object_type: SchemaObjectType::User,
-                object_id: user_def.id as _,
-                grantee_id: user_def.id,
+            PrivilegeDef::new(
+                PrivilegeType::Alter,
+                SchemaObjectType::User,
+                user_def.id as _,
+                user_def.id,
                 grantor_id,
-                schema_version: user_def.schema_version,
-            },
+                user_def.schema_version,
+            )
+            .expect("valid"),
         ]
     }
 
@@ -2927,8 +2934,8 @@ pub mod acl {
     ) -> tarantool::Result<()> {
         storage.privileges.insert(priv_def)?;
 
-        let privilege = &priv_def.privilege;
-        let (object, object_type) = (priv_def.object_id(), &priv_def.object_type);
+        let privilege = priv_def.privilege();
+        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()) {
@@ -2972,14 +2979,14 @@ pub mod acl {
     ) -> tarantool::Result<()> {
         // FIXME: currently there's no way to revoke a default privilege
         storage.privileges.delete(
-            priv_def.grantee_id,
-            &priv_def.object_type,
-            priv_def.object_id,
-            &priv_def.privilege,
+            priv_def.grantee_id(),
+            &priv_def.object_type(),
+            priv_def.object_id_raw(),
+            &priv_def.privilege(),
         )?;
 
-        let privilege = &priv_def.privilege;
-        let (object, object_type) = (priv_def.object_id(), &priv_def.object_type);
+        let privilege = priv_def.privilege();
+        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()) {
@@ -3131,9 +3138,9 @@ pub mod acl {
                 box.schema.role.grant(grantee_id, privilege, object_type, object_id)
             end",
             (
-                priv_def.grantee_id,
-                &priv_def.privilege,
-                &priv_def.object_type.into_tarantool(),
+                priv_def.grantee_id(),
+                &priv_def.privilege(),
+                &priv_def.object_type().into_tarantool(),
                 priv_def.object_id(),
             ),
         )
@@ -3158,9 +3165,9 @@ pub mod acl {
                 box.schema.role.revoke(grantee_id, privilege, object_type, object_id)
             end",
             (
-                priv_def.grantee_id,
-                &priv_def.privilege,
-                &priv_def.object_type.into_tarantool(),
+                priv_def.grantee_id(),
+                &priv_def.privilege(),
+                &priv_def.object_type().into_tarantool(),
                 priv_def.object_id(),
             ),
         )
diff --git a/src/traft/op.rs b/src/traft/op.rs
index 3cbaf2972d..ea6842b963 100644
--- a/src/traft/op.rs
+++ b/src/traft/op.rs
@@ -153,27 +153,27 @@ impl std::fmt::Display for Op {
             }
             Self::Acl(Acl::GrantPrivilege { priv_def }) => {
                 let object_id = priv_def.object_id();
-                let PrivilegeDef {
-                    grantee_id,
-                    grantor_id,
-                    object_type,
-                    privilege,
-                    schema_version,
-                    ..
-                } = priv_def;
-                write!(f, "GrantPrivilege({schema_version}, {grantor_id}, {grantee_id}, {object_type}, {object_id:?}, {privilege})")
+
+                write!(
+                    f,
+                    "GrantPrivilege({schema_version}, {grantor_id}, {grantee_id}, {object_type}, {object_id:?}, {privilege})", 
+                    schema_version = priv_def.schema_version(),
+                    grantor_id = priv_def.grantor_id(),
+                    grantee_id = priv_def.grantee_id(),
+                    object_type = priv_def.object_type(),
+                    privilege = priv_def.privilege(),
+                )
             }
             Self::Acl(Acl::RevokePrivilege { priv_def }) => {
                 let object_id = priv_def.object_id();
-                let PrivilegeDef {
-                    grantee_id,
-                    grantor_id,
-                    object_type,
-                    privilege,
-                    schema_version,
-                    ..
-                } = priv_def;
-                write!(f, "RevokePrivilege({schema_version}, {grantor_id}, {grantee_id}, {object_type}, {object_id:?}, {privilege})")
+                write!(
+                    f,
+                    "RevokePrivilege({schema_version}, {grantor_id}, {grantee_id}, {object_type}, {object_id:?}, {privilege})",
+                    schema_version = priv_def.schema_version(),
+                    grantor_id = priv_def.grantor_id(),
+                    grantee_id = priv_def.grantee_id(),
+                    object_type = priv_def.object_type(),
+                    privilege = priv_def.privilege(),)
             }
         };
 
@@ -270,19 +270,6 @@ impl Op {
             Self::DdlPrepare { .. } | Self::Acl(_) => true,
         }
     }
-
-    #[inline]
-    pub fn set_schema_version(&mut self, new_schema_version: u64) {
-        match self {
-            Self::Nop | Self::Dml(_) | Self::DdlAbort | Self::DdlCommit => {}
-            Self::DdlPrepare { schema_version, .. } => {
-                *schema_version = new_schema_version;
-            }
-            Self::Acl(acl) => {
-                acl.set_schema_version(new_schema_version);
-            }
-        }
-    }
 }
 
 // TODO: remove this
@@ -576,20 +563,8 @@ impl Acl {
             Self::DropUser { schema_version, .. } => *schema_version,
             Self::CreateRole { role_def, .. } => role_def.schema_version,
             Self::DropRole { schema_version, .. } => *schema_version,
-            Self::GrantPrivilege { priv_def } => priv_def.schema_version,
-            Self::RevokePrivilege { priv_def } => priv_def.schema_version,
-        }
-    }
-
-    pub fn set_schema_version(&mut self, new_schema_version: u64) {
-        match self {
-            Self::CreateUser { user_def } => user_def.schema_version = new_schema_version,
-            Self::ChangeAuth { schema_version, .. } => *schema_version = new_schema_version,
-            Self::DropUser { schema_version, .. } => *schema_version = new_schema_version,
-            Self::CreateRole { role_def, .. } => role_def.schema_version = new_schema_version,
-            Self::DropRole { schema_version, .. } => *schema_version = new_schema_version,
-            Self::GrantPrivilege { priv_def } => priv_def.schema_version = new_schema_version,
-            Self::RevokePrivilege { priv_def } => priv_def.schema_version = new_schema_version,
+            Self::GrantPrivilege { priv_def } => priv_def.schema_version(),
+            Self::RevokePrivilege { priv_def } => priv_def.schema_version(),
         }
     }
 }
-- 
GitLab