1use vibesql_ast::DropTableStmt;
4use vibesql_storage::Database;
5
6use crate::{errors::ExecutorError, privilege_checker::PrivilegeChecker};
7
8pub struct DropTableExecutor;
10
11impl DropTableExecutor {
12 pub fn execute(stmt: &DropTableStmt, database: &mut Database) -> Result<String, ExecutorError> {
57 let table_exists = database.catalog.table_exists(&stmt.table_name);
59
60 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_exists {
67 return Err(ExecutorError::TableNotFound(stmt.table_name.clone()));
68 }
69
70 PrivilegeChecker::check_drop(database, &stmt.table_name)?;
72
73 let dropped_indexes = database.catalog.drop_table_indexes(&stmt.table_name);
75 let index_count = dropped_indexes.len();
76
77 for index in &dropped_indexes {
79 let _ = database.drop_index(&index.name);
81 let _ = database.drop_spatial_index(&index.name);
83 }
84
85 database
87 .drop_table(&stmt.table_name)
88 .map_err(|e| ExecutorError::StorageError(e.to_string()))?;
89
90 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 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 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 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 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 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 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 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 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 assert_eq!(db.get_table("customers").unwrap().row_count(), 1);
255
256 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 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 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 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 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 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 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 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 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 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 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 assert!(db.index_exists("idx_users_email"));
441 assert!(db.index_exists("idx_users_id"));
442
443 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 assert!(!db.catalog.table_exists("users"));
451
452 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 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 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 assert!(db.index_exists("idx_products_name"));
512
513 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 assert!(!db.catalog.table_exists("products"));
520 assert!(!db.index_exists("idx_products_name"));
521
522 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
524
525 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 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 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}