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