vibesql_executor/
revoke.rs

1//! REVOKE statement executor
2
3use vibesql_ast::*;
4use vibesql_storage::Database;
5
6use crate::errors::ExecutorError;
7
8/// Executor for REVOKE statements
9pub struct RevokeExecutor;
10
11impl RevokeExecutor {
12    /// Execute REVOKE statement
13    ///
14    /// Phase 3: Supports CASCADE/RESTRICT and GRANT OPTION FOR
15    pub fn execute_revoke(
16        stmt: &RevokeStmt,
17        database: &mut Database,
18    ) -> Result<String, ExecutorError> {
19        // Validate object exists based on object type
20        match stmt.object_type {
21            ObjectType::Table => {
22                if !database.catalog.table_exists(&stmt.object_name) {
23                    return Err(ExecutorError::TableNotFound(stmt.object_name.clone()));
24                }
25            }
26            ObjectType::Schema => {
27                if !database.catalog.schema_exists(&stmt.object_name) {
28                    return Err(ExecutorError::SchemaNotFound(stmt.object_name.clone()));
29                }
30            }
31            // SQL:1999 Feature F031: New object types
32            // Accept without validation - implementations are future work
33            ObjectType::Domain
34            | ObjectType::Collation
35            | ObjectType::CharacterSet
36            | ObjectType::Translation
37            | ObjectType::Type
38            | ObjectType::Sequence => {
39                // No validation - these objects are stubbed for conformance
40            }
41            // Functions (SQL:1999 Feature P001)
42            ObjectType::Function | ObjectType::SpecificFunction => {
43                // Allow REVOKE on non-existent functions (SQL:1999 compliance)
44                // No validation - silently succeed if function doesn't exist
45            }
46            // Procedures (SQL:1999 Feature P001)
47            ObjectType::Procedure | ObjectType::SpecificProcedure => {
48                // Allow REVOKE on non-existent procedures (SQL:1999 compliance)
49                // No validation - silently succeed if procedure doesn't exist
50            }
51            // Routine (generic term for function or procedure)
52            ObjectType::Routine | ObjectType::SpecificRoutine => {
53                // Allow REVOKE on non-existent routines (SQL:1999 compliance)
54                // No validation - silently succeed if routine doesn't exist
55            }
56            // Methods (SQL:1999 Feature S091) - OOP SQL
57            // For now, accept without validation - full UDT implementation is future work
58            ObjectType::Method
59            | ObjectType::ConstructorMethod
60            | ObjectType::StaticMethod
61            | ObjectType::InstanceMethod
62            | ObjectType::SpecificMethod
63            | ObjectType::SpecificConstructorMethod
64            | ObjectType::SpecificStaticMethod
65            | ObjectType::SpecificInstanceMethod => {
66                // No validation - methods belong to UDTs which aren't fully implemented yet
67                // Privilege tracking works by object name alone
68            }
69        }
70
71        // Validate all grantees (roles) exist
72        for grantee in &stmt.grantees {
73            if !database.catalog.role_exists(grantee) {
74                return Err(ExecutorError::RoleNotFound(grantee.clone()));
75            }
76        }
77
78        // Expand ALL PRIVILEGES based on object type
79        let expanded_privileges = if stmt.privileges.contains(&PrivilegeType::AllPrivileges) {
80            match stmt.object_type {
81                ObjectType::Table => vec![
82                    PrivilegeType::Select(None),
83                    PrivilegeType::Insert(None),
84                    PrivilegeType::Update(None),
85                    PrivilegeType::Delete,
86                    PrivilegeType::References(None),
87                ],
88                ObjectType::Schema => vec![PrivilegeType::Usage, PrivilegeType::Create],
89                // USAGE-only objects (domains, collations, character sets, translations, types,
90                // sequences) ALL PRIVILEGES on these objects means USAGE privilege
91                ObjectType::Domain
92                | ObjectType::Collation
93                | ObjectType::CharacterSet
94                | ObjectType::Translation
95                | ObjectType::Type
96                | ObjectType::Sequence => vec![PrivilegeType::Usage],
97                // Callable objects (functions, procedures, routines, methods)
98                // ALL PRIVILEGES on callable objects means EXECUTE privilege
99                ObjectType::Function
100                | ObjectType::Procedure
101                | ObjectType::Routine
102                | ObjectType::Method
103                | ObjectType::ConstructorMethod
104                | ObjectType::StaticMethod
105                | ObjectType::InstanceMethod
106                | ObjectType::SpecificFunction
107                | ObjectType::SpecificProcedure
108                | ObjectType::SpecificRoutine
109                | ObjectType::SpecificMethod
110                | ObjectType::SpecificConstructorMethod
111                | ObjectType::SpecificStaticMethod
112                | ObjectType::SpecificInstanceMethod => vec![PrivilegeType::Execute],
113            }
114        } else {
115            stmt.privileges.clone()
116        };
117
118        // For RESTRICT, check for dependent grants before making any changes
119        if matches!(stmt.cascade_option, CascadeOption::Restrict) {
120            for grantee in &stmt.grantees {
121                for privilege in &expanded_privileges {
122                    if database.catalog.has_dependent_grants(&stmt.object_name, grantee, privilege)
123                    {
124                        return Err(ExecutorError::DependentPrivilegesExist(format!(
125                            "Cannot revoke {:?} on {} from {} - dependent grants exist (use CASCADE to revoke dependent grants)",
126                            privilege,
127                            stmt.object_name,
128                            grantee
129                        )));
130                    }
131                }
132            }
133        }
134
135        // Revoke privileges for each grantee and privilege
136        let mut total_removed = 0;
137        for grantee in &stmt.grantees {
138            for privilege in &expanded_privileges {
139                // Remove the grant
140                let removed = database.catalog.remove_grants(
141                    &stmt.object_name,
142                    grantee,
143                    privilege,
144                    stmt.grant_option_for,
145                );
146                total_removed += removed;
147
148                // If CASCADE, recursively revoke from dependent grantees
149                if matches!(stmt.cascade_option, CascadeOption::Cascade) {
150                    Self::revoke_cascade(
151                        database,
152                        &stmt.object_name,
153                        grantee,
154                        privilege,
155                        stmt.grant_option_for,
156                    )?;
157                }
158            }
159        }
160
161        let grantees_str = stmt.grantees.join(", ");
162        let privileges_str =
163            stmt.privileges.iter().map(|p| format!("{:?}", p)).collect::<Vec<_>>().join(", ");
164
165        let action = if stmt.grant_option_for { "Revoked grant option for" } else { "Revoked" };
166
167        Ok(format!(
168            "{} {} on {} from {} ({} grants affected)",
169            action, privileges_str, stmt.object_name, grantees_str, total_removed
170        ))
171    }
172
173    /// Recursively revoke privileges from dependent grantees (CASCADE behavior)
174    fn revoke_cascade(
175        database: &mut Database,
176        object: &str,
177        grantor: &str,
178        privilege: &PrivilegeType,
179        grant_option_only: bool,
180    ) -> Result<(), ExecutorError> {
181        // Find all grants that were made by this grantor
182        let dependent_grants: Vec<String> = database
183            .catalog
184            .get_all_grants()
185            .iter()
186            .filter(|g| g.object == object && g.grantor == grantor && g.privilege == *privilege)
187            .map(|g| g.grantee.clone())
188            .collect();
189
190        // Recursively revoke from each dependent grantee
191        for dependent_grantee in dependent_grants {
192            database.catalog.remove_grants(
193                object,
194                &dependent_grantee,
195                privilege,
196                grant_option_only,
197            );
198
199            // Recursively cascade
200            Self::revoke_cascade(
201                database,
202                object,
203                &dependent_grantee,
204                privilege,
205                grant_option_only,
206            )?;
207        }
208
209        Ok(())
210    }
211}