vibesql_executor/
drop_table.rs

1//! DROP TABLE statement execution
2
3use vibesql_ast::DropTableStmt;
4use vibesql_storage::Database;
5
6use crate::{errors::ExecutorError, privilege_checker::PrivilegeChecker};
7
8/// Executor for DROP TABLE statements
9pub struct DropTableExecutor;
10
11impl DropTableExecutor {
12    /// Execute a DROP TABLE statement
13    ///
14    /// # Arguments
15    ///
16    /// * `stmt` - The DROP TABLE statement AST node
17    /// * `database` - The database to drop the table from
18    ///
19    /// # Returns
20    ///
21    /// Success message or error
22    ///
23    /// # Examples
24    ///
25    /// ```
26    /// use vibesql_ast::{ColumnDef, CreateTableStmt, DropTableStmt};
27    /// use vibesql_executor::{CreateTableExecutor, DropTableExecutor};
28    /// use vibesql_storage::Database;
29    /// use vibesql_types::DataType;
30    ///
31    /// let mut db = Database::new();
32    /// let create_stmt = CreateTableStmt {
33    ///     if_not_exists: false,
34    ///     table_name: "users".to_string(),
35    ///     columns: vec![ColumnDef {
36    ///         name: "id".to_string(),
37    ///         data_type: DataType::Integer,
38    ///         nullable: false,
39    ///         constraints: vec![],
40    ///         default_value: None,
41    ///         comment: None,
42    ///     }],
43    ///     table_constraints: vec![],
44    ///     table_options: vec![],
45    /// };
46    /// CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
47    ///
48    /// let stmt = DropTableStmt { table_name: "users".to_string(), if_exists: false };
49    ///
50    /// let result = DropTableExecutor::execute(&stmt, &mut db);
51    /// assert!(result.is_ok());
52    /// ```
53    pub fn execute(stmt: &DropTableStmt, database: &mut Database) -> Result<String, ExecutorError> {
54        // Check if table exists
55        let table_exists = database.catalog.table_exists(&stmt.table_name);
56
57        // If IF EXISTS is specified and table doesn't exist, succeed silently
58        if stmt.if_exists && !table_exists {
59            return Ok(format!("Table '{}' does not exist (IF EXISTS specified)", stmt.table_name));
60        }
61
62        // If table doesn't exist and IF EXISTS is not specified, return error
63        if !table_exists {
64            return Err(ExecutorError::TableNotFound(stmt.table_name.clone()));
65        }
66
67        // Check DROP privilege on the table
68        PrivilegeChecker::check_drop(database, &stmt.table_name)?;
69
70        // Drop all indexes associated with this table first
71        let dropped_indexes = database.catalog.drop_table_indexes(&stmt.table_name);
72        let index_count = dropped_indexes.len();
73
74        // Drop physical indexes from storage
75        for index in &dropped_indexes {
76            // Try to drop from B-tree storage (ignore errors if not found)
77            let _ = database.drop_index(&index.name);
78            // Try to drop from spatial storage (ignore errors if not found)
79            let _ = database.drop_spatial_index(&index.name);
80        }
81
82        // Drop the table from storage (this also removes from catalog)
83        database
84            .drop_table(&stmt.table_name)
85            .map_err(|e| ExecutorError::StorageError(e.to_string()))?;
86
87        // Return success message
88        if index_count > 0 {
89            Ok(format!(
90                "Table '{}' and {} associated index(es) dropped successfully",
91                stmt.table_name, index_count
92            ))
93        } else {
94            Ok(format!("Table '{}' dropped successfully", stmt.table_name))
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use vibesql_ast::{ColumnDef, CreateTableStmt};
102    use vibesql_types::DataType;
103
104    use super::*;
105    use crate::CreateTableExecutor;
106
107    #[test]
108    fn test_drop_existing_table() {
109        let mut db = Database::new();
110
111        // Create a table first
112        let create_stmt = CreateTableStmt {
113        if_not_exists: false,
114            table_name: "users".to_string(),
115            columns: vec![ColumnDef {
116                name: "id".to_string(),
117                data_type: DataType::Integer,
118                nullable: false,
119                constraints: vec![],
120                default_value: None,
121                comment: None,
122            }],
123            table_constraints: vec![],
124            table_options: vec![],
125        };
126        CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
127        assert!(db.catalog.table_exists("users"));
128
129        // Now drop it
130        let drop_stmt = DropTableStmt { table_name: "users".to_string(), if_exists: false };
131
132        let result = DropTableExecutor::execute(&drop_stmt, &mut db);
133        assert!(result.is_ok());
134        assert_eq!(result.unwrap(), "Table 'users' dropped successfully");
135
136        // Verify table no longer exists
137        assert!(!db.catalog.table_exists("users"));
138        assert!(db.get_table("users").is_none());
139    }
140
141    #[test]
142    fn test_drop_nonexistent_table_without_if_exists() {
143        let mut db = Database::new();
144
145        let drop_stmt = DropTableStmt { table_name: "nonexistent".to_string(), if_exists: false };
146
147        let result = DropTableExecutor::execute(&drop_stmt, &mut db);
148        assert!(result.is_err());
149        assert!(matches!(result, Err(ExecutorError::TableNotFound(_))));
150    }
151
152    #[test]
153    fn test_drop_nonexistent_table_with_if_exists() {
154        let mut db = Database::new();
155
156        let drop_stmt = DropTableStmt { table_name: "nonexistent".to_string(), if_exists: true };
157
158        let result = DropTableExecutor::execute(&drop_stmt, &mut db);
159        assert!(result.is_ok());
160        assert_eq!(result.unwrap(), "Table 'nonexistent' does not exist (IF EXISTS specified)");
161    }
162
163    #[test]
164    fn test_drop_existing_table_with_if_exists() {
165        let mut db = Database::new();
166
167        // Create a table first
168        let create_stmt = CreateTableStmt {
169        if_not_exists: false,
170            table_name: "products".to_string(),
171            columns: vec![ColumnDef {
172                name: "id".to_string(),
173                data_type: DataType::Integer,
174                nullable: false,
175                constraints: vec![],
176                default_value: None,
177                comment: None,
178            }],
179            table_constraints: vec![],
180            table_options: vec![],
181        };
182        CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
183
184        // Drop it with IF EXISTS
185        let drop_stmt = DropTableStmt { table_name: "products".to_string(), if_exists: true };
186
187        let result = DropTableExecutor::execute(&drop_stmt, &mut db);
188        assert!(result.is_ok());
189        assert_eq!(result.unwrap(), "Table 'products' dropped successfully");
190
191        // Verify table no longer exists
192        assert!(!db.catalog.table_exists("products"));
193    }
194
195    #[test]
196    fn test_drop_table_with_data() {
197        let mut db = Database::new();
198
199        // Create a table with data
200        let create_stmt = CreateTableStmt {
201        if_not_exists: false,
202            table_name: "customers".to_string(),
203            columns: vec![
204                ColumnDef {
205                    name: "id".to_string(),
206                    data_type: DataType::Integer,
207                    nullable: false,
208                    constraints: vec![],
209                    default_value: None,
210                    comment: None,
211                },
212                ColumnDef {
213                    name: "name".to_string(),
214                    data_type: DataType::Varchar { max_length: Some(100) },
215                    nullable: false,
216                    constraints: vec![],
217                    default_value: None,
218                    comment: None,
219                },
220            ],
221            table_constraints: vec![],
222            table_options: vec![],
223        };
224        CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
225
226        // Insert some data
227        use vibesql_storage::Row;
228        use vibesql_types::SqlValue;
229        let row = Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))]);
230        db.insert_row("customers", row).unwrap();
231
232        // Verify data exists
233        assert_eq!(db.get_table("customers").unwrap().row_count(), 1);
234
235        // Drop the table
236        let drop_stmt = DropTableStmt { table_name: "customers".to_string(), if_exists: false };
237
238        let result = DropTableExecutor::execute(&drop_stmt, &mut db);
239        assert!(result.is_ok());
240
241        // Verify table and data are gone
242        assert!(!db.catalog.table_exists("customers"));
243        assert!(db.get_table("customers").is_none());
244    }
245
246    #[test]
247    fn test_drop_and_recreate_table() {
248        let mut db = Database::new();
249
250        // Create table
251        let create_stmt = CreateTableStmt {
252        if_not_exists: false,
253            table_name: "temp".to_string(),
254            columns: vec![ColumnDef {
255                name: "id".to_string(),
256                data_type: DataType::Integer,
257                nullable: false,
258                constraints: vec![],
259                default_value: None,
260                comment: None,
261            }],
262            table_constraints: vec![],
263            table_options: vec![],
264        };
265        CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
266
267        // Drop it
268        let drop_stmt = DropTableStmt { table_name: "temp".to_string(), if_exists: false };
269        DropTableExecutor::execute(&drop_stmt, &mut db).unwrap();
270
271        // Recreate it
272        let result = CreateTableExecutor::execute(&create_stmt, &mut db);
273        assert!(result.is_ok());
274        assert!(db.catalog.table_exists("temp"));
275    }
276
277    #[test]
278    fn test_drop_multiple_tables() {
279        let mut db = Database::new();
280
281        // Create multiple tables
282        for name in &["table1", "table2", "table3"] {
283            let create_stmt = CreateTableStmt {
284        if_not_exists: false,
285                table_name: name.to_string(),
286                columns: vec![ColumnDef {
287                    name: "id".to_string(),
288                    data_type: DataType::Integer,
289                    nullable: false,
290                    constraints: vec![],
291                    default_value: None,
292                    comment: None,
293                }],
294                table_constraints: vec![],
295                table_options: vec![],
296            };
297            CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
298        }
299
300        assert_eq!(db.list_tables().len(), 3);
301
302        // Drop them one by one
303        for name in &["table1", "table2", "table3"] {
304            let drop_stmt = DropTableStmt { table_name: name.to_string(), if_exists: false };
305            let result = DropTableExecutor::execute(&drop_stmt, &mut db);
306            assert!(result.is_ok());
307        }
308
309        assert_eq!(db.list_tables().len(), 0);
310    }
311
312    #[test]
313    fn test_drop_table_case_sensitivity() {
314        let mut db = Database::new();
315
316        // Create table with specific case
317        let create_stmt = CreateTableStmt {
318        if_not_exists: false,
319            table_name: "MyTable".to_string(),
320            columns: vec![ColumnDef {
321                name: "id".to_string(),
322                data_type: DataType::Integer,
323                nullable: false,
324                constraints: vec![],
325                default_value: None,
326                comment: None,
327            }],
328            table_constraints: vec![],
329            table_options: vec![],
330        };
331        CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
332
333        // Try to drop with exact case - should succeed
334        let drop_stmt = DropTableStmt { table_name: "MyTable".to_string(), if_exists: false };
335        let result = DropTableExecutor::execute(&drop_stmt, &mut db);
336        assert!(result.is_ok());
337    }
338
339    #[test]
340    fn test_drop_table_cascades_to_indexes() {
341        use crate::CreateIndexExecutor;
342        use vibesql_ast::{CreateIndexStmt, IndexColumn, OrderDirection};
343
344        let mut db = Database::new();
345
346        // Create table
347        let create_stmt = CreateTableStmt {
348        if_not_exists: false,
349            table_name: "users".to_string(),
350            columns: vec![
351                ColumnDef {
352                    name: "id".to_string(),
353                    data_type: DataType::Integer,
354                    nullable: false,
355                    constraints: vec![],
356                    default_value: None,
357                    comment: None,
358                },
359                ColumnDef {
360                    name: "email".to_string(),
361                    data_type: DataType::Varchar { max_length: Some(255) },
362                    nullable: false,
363                    constraints: vec![],
364                    default_value: None,
365                    comment: None,
366                },
367            ],
368            table_constraints: vec![],
369            table_options: vec![],
370        };
371        CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
372
373        // Create indexes on the table
374        let index1_stmt = CreateIndexStmt {
375            index_name: "idx_users_email".to_string(),
376            if_not_exists: false,
377            table_name: "users".to_string(),
378            index_type: vibesql_ast::IndexType::BTree { unique: false },
379            columns: vec![IndexColumn {
380                column_name: "email".to_string(),
381                prefix_length: None,
382                direction: OrderDirection::Asc,
383            }],
384        };
385        CreateIndexExecutor::execute(&index1_stmt, &mut db).unwrap();
386
387        let index2_stmt = CreateIndexStmt {
388            index_name: "idx_users_id".to_string(),
389            if_not_exists: false,
390            table_name: "users".to_string(),
391            index_type: vibesql_ast::IndexType::BTree { unique: false },
392            columns: vec![IndexColumn {
393                column_name: "id".to_string(),
394                prefix_length: None,
395                direction: OrderDirection::Asc,
396            }],
397        };
398        CreateIndexExecutor::execute(&index2_stmt, &mut db).unwrap();
399
400        // Verify indexes exist
401        assert!(db.index_exists("idx_users_email"));
402        assert!(db.index_exists("idx_users_id"));
403
404        // Drop the table
405        let drop_stmt = DropTableStmt { table_name: "users".to_string(), if_exists: false };
406        let result = DropTableExecutor::execute(&drop_stmt, &mut db);
407        assert!(result.is_ok());
408
409        // Verify table is dropped
410        assert!(!db.catalog.table_exists("users"));
411
412        // Verify indexes are also dropped (CASCADE behavior)
413        assert!(!db.index_exists("idx_users_email"));
414        assert!(!db.index_exists("idx_users_id"));
415    }
416
417    #[test]
418    fn test_drop_and_recreate_table_with_same_index_names() {
419        use crate::CreateIndexExecutor;
420        use vibesql_ast::{CreateIndexStmt, IndexColumn, OrderDirection};
421
422        let mut db = Database::new();
423
424        // Create table
425        let create_stmt = CreateTableStmt {
426        if_not_exists: false,
427            table_name: "products".to_string(),
428            columns: vec![
429                ColumnDef {
430                    name: "id".to_string(),
431                    data_type: DataType::Integer,
432                    nullable: false,
433                    constraints: vec![],
434                    default_value: None,
435                    comment: None,
436                },
437                ColumnDef {
438                    name: "name".to_string(),
439                    data_type: DataType::Varchar { max_length: Some(100) },
440                    nullable: false,
441                    constraints: vec![],
442                    default_value: None,
443                    comment: None,
444                },
445            ],
446            table_constraints: vec![],
447            table_options: vec![],
448        };
449        CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
450
451        // Create index
452        let index_stmt = CreateIndexStmt {
453            index_name: "idx_products_name".to_string(),
454            if_not_exists: false,
455            table_name: "products".to_string(),
456            index_type: vibesql_ast::IndexType::BTree { unique: false },
457            columns: vec![IndexColumn {
458                column_name: "name".to_string(),
459                prefix_length: None,
460                direction: OrderDirection::Asc,
461            }],
462        };
463        CreateIndexExecutor::execute(&index_stmt, &mut db).unwrap();
464
465        // Verify index exists
466        assert!(db.index_exists("idx_products_name"));
467
468        // Drop the table (should cascade to drop index)
469        let drop_stmt = DropTableStmt { table_name: "products".to_string(), if_exists: false };
470        DropTableExecutor::execute(&drop_stmt, &mut db).unwrap();
471
472        // Verify both table and index are dropped
473        assert!(!db.catalog.table_exists("products"));
474        assert!(!db.index_exists("idx_products_name"));
475
476        // Recreate table with same name
477        CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
478
479        // Create index with same name - should succeed (no IndexAlreadyExists error)
480        let result = CreateIndexExecutor::execute(&index_stmt, &mut db);
481        assert!(result.is_ok(), "Should be able to recreate index with same name after table drop");
482        assert!(db.index_exists("idx_products_name"));
483    }
484
485    #[test]
486    fn test_drop_table_without_indexes() {
487        let mut db = Database::new();
488
489        // Create table without indexes
490        let create_stmt = CreateTableStmt {
491        if_not_exists: false,
492            table_name: "simple_table".to_string(),
493            columns: vec![ColumnDef {
494                name: "id".to_string(),
495                data_type: DataType::Integer,
496                nullable: false,
497                constraints: vec![],
498                default_value: None,
499                comment: None,
500            }],
501            table_constraints: vec![],
502            table_options: vec![],
503        };
504        CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
505
506        // Drop table without indexes - should still work
507        let drop_stmt = DropTableStmt { table_name: "simple_table".to_string(), if_exists: false };
508        let result = DropTableExecutor::execute(&drop_stmt, &mut db);
509        assert!(result.is_ok(), "Dropping table without indexes should still work");
510        assert!(!db.catalog.table_exists("simple_table"));
511    }
512}