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