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> {
58 if crate::sqlite_schema::is_sqlite_schema_table(&stmt.table_name) {
67 return Err(ExecutorError::SqliteCompatError(
68 "table sqlite_master may not be dropped".to_string(),
69 ));
70 }
71
72 let table_exists = database.catalog.table_exists(&stmt.table_name);
74
75 if stmt.if_exists && !table_exists {
77 return Ok(format!("Table '{}' does not exist (IF EXISTS specified)", stmt.table_name));
78 }
79
80 if !table_exists {
82 return Err(ExecutorError::TableNotFound(stmt.table_name.clone()));
83 }
84
85 PrivilegeChecker::check_drop(database, &stmt.table_name)?;
87
88 let dropped_indexes = database.catalog.drop_table_indexes(&stmt.table_name);
90 let index_count = dropped_indexes.len();
91
92 for index in &dropped_indexes {
98 let qualified = format!("{}.{}", index.schema(), index.name);
99 let _ = database.drop_index(&qualified);
101 let _ = database.drop_spatial_index(&qualified);
106 }
107
108 let dropped_triggers = database.catalog.drop_table_triggers(&stmt.table_name);
116 let trigger_count = dropped_triggers.len();
117
118 database
120 .drop_table(&stmt.table_name)
121 .map_err(|e| ExecutorError::StorageError(e.to_string()))?;
122
123 match (index_count, trigger_count) {
125 (0, 0) => Ok(format!("Table '{}' dropped successfully", stmt.table_name)),
126 (i, 0) => Ok(format!(
127 "Table '{}' and {} associated index(es) dropped successfully",
128 stmt.table_name, i
129 )),
130 (0, t) => Ok(format!(
131 "Table '{}' and {} associated trigger(s) dropped successfully",
132 stmt.table_name, t
133 )),
134 (i, t) => Ok(format!(
135 "Table '{}', {} associated index(es), and {} associated trigger(s) dropped successfully",
136 stmt.table_name, i, t
137 )),
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use vibesql_ast::{ColumnDef, CreateTableStmt};
145 use vibesql_types::DataType;
146
147 use super::*;
148 use crate::CreateTableExecutor;
149
150 #[test]
151 fn test_drop_existing_table() {
152 let mut db = Database::new();
153
154 let create_stmt = CreateTableStmt {
156 temporary: false,
157 if_not_exists: false,
158 table_name: "users".to_string(),
159 columns: vec![ColumnDef {
160 name: "id".to_string(),
161 data_type: DataType::Integer,
162 nullable: false,
163 constraints: vec![],
164 default_value: None,
165 comment: None,
166 generated_expr: None,
167 is_exact_integer_type: false,
168 }],
169 table_constraints: vec![],
170 table_options: vec![],
171 quoted: false,
172 name_source: None,
173 as_query: None,
174 without_rowid: false,
175 };
176 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
177 assert!(db.catalog.table_exists("users"));
178
179 let drop_stmt =
181 DropTableStmt { table_name: "users".to_string(), if_exists: false, quoted: false };
182
183 let result = DropTableExecutor::execute(&drop_stmt, &mut db);
184 assert!(result.is_ok());
185 assert_eq!(result.unwrap(), "Table 'users' dropped successfully");
186
187 assert!(!db.catalog.table_exists("users"));
189 assert!(db.get_table("users").is_none());
190 }
191
192 #[test]
193 fn test_drop_nonexistent_table_without_if_exists() {
194 let mut db = Database::new();
195
196 let drop_stmt = DropTableStmt {
197 table_name: "nonexistent".to_string(),
198 if_exists: false,
199 quoted: false,
200 };
201
202 let result = DropTableExecutor::execute(&drop_stmt, &mut db);
203 assert!(result.is_err());
204 assert!(matches!(result, Err(ExecutorError::TableNotFound(_))));
205 }
206
207 #[test]
208 fn test_drop_nonexistent_table_with_if_exists() {
209 let mut db = Database::new();
210
211 let drop_stmt =
212 DropTableStmt { table_name: "nonexistent".to_string(), if_exists: true, quoted: false };
213
214 let result = DropTableExecutor::execute(&drop_stmt, &mut db);
215 assert!(result.is_ok());
216 assert_eq!(result.unwrap(), "Table 'nonexistent' does not exist (IF EXISTS specified)");
217 }
218
219 #[test]
220 fn test_drop_existing_table_with_if_exists() {
221 let mut db = Database::new();
222
223 let create_stmt = CreateTableStmt {
225 temporary: false,
226 if_not_exists: false,
227 table_name: "products".to_string(),
228 columns: vec![ColumnDef {
229 name: "id".to_string(),
230 data_type: DataType::Integer,
231 nullable: false,
232 constraints: vec![],
233 default_value: None,
234 comment: None,
235 generated_expr: None,
236 is_exact_integer_type: false,
237 }],
238 table_constraints: vec![],
239 table_options: vec![],
240 quoted: false,
241 name_source: None,
242 as_query: None,
243 without_rowid: false,
244 };
245 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
246
247 let drop_stmt =
249 DropTableStmt { table_name: "products".to_string(), if_exists: true, quoted: false };
250
251 let result = DropTableExecutor::execute(&drop_stmt, &mut db);
252 assert!(result.is_ok());
253 assert_eq!(result.unwrap(), "Table 'products' dropped successfully");
254
255 assert!(!db.catalog.table_exists("products"));
257 }
258
259 #[test]
260 fn test_drop_table_with_data() {
261 let mut db = Database::new();
262
263 let create_stmt = CreateTableStmt {
265 temporary: false,
266 if_not_exists: false,
267 table_name: "customers".to_string(),
268 columns: vec![
269 ColumnDef {
270 name: "id".to_string(),
271 data_type: DataType::Integer,
272 nullable: false,
273 constraints: vec![],
274 default_value: None,
275 comment: None,
276 generated_expr: None,
277 is_exact_integer_type: false,
278 },
279 ColumnDef {
280 name: "name".to_string(),
281 data_type: DataType::Varchar { max_length: Some(100) },
282 nullable: false,
283 constraints: vec![],
284 default_value: None,
285 comment: None,
286 generated_expr: None,
287 is_exact_integer_type: false,
288 },
289 ],
290 table_constraints: vec![],
291 table_options: vec![],
292 quoted: false,
293 name_source: None,
294 as_query: None,
295 without_rowid: false,
296 };
297 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
298
299 use vibesql_storage::Row;
301 use vibesql_types::SqlValue;
302 let row =
303 Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))]);
304 db.insert_row("customers", row).unwrap();
305
306 assert_eq!(db.get_table("customers").unwrap().row_count(), 1);
308
309 let drop_stmt =
311 DropTableStmt { table_name: "customers".to_string(), if_exists: false, quoted: false };
312
313 let result = DropTableExecutor::execute(&drop_stmt, &mut db);
314 assert!(result.is_ok());
315
316 assert!(!db.catalog.table_exists("customers"));
318 assert!(db.get_table("customers").is_none());
319 }
320
321 #[test]
322 fn test_drop_and_recreate_table() {
323 let mut db = Database::new();
324
325 let create_stmt = CreateTableStmt {
327 temporary: false,
328 if_not_exists: false,
329 table_name: "temp".to_string(),
330 columns: vec![ColumnDef {
331 name: "id".to_string(),
332 data_type: DataType::Integer,
333 nullable: false,
334 constraints: vec![],
335 default_value: None,
336 comment: None,
337 generated_expr: None,
338 is_exact_integer_type: false,
339 }],
340 table_constraints: vec![],
341 table_options: vec![],
342 quoted: false,
343 name_source: None,
344 as_query: None,
345 without_rowid: false,
346 };
347 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
348
349 let drop_stmt =
351 DropTableStmt { table_name: "temp".to_string(), if_exists: false, quoted: false };
352 DropTableExecutor::execute(&drop_stmt, &mut db).unwrap();
353
354 let result = CreateTableExecutor::execute(&create_stmt, &mut db);
356 assert!(result.is_ok());
357 assert!(db.catalog.table_exists("temp"));
358 }
359
360 #[test]
361 fn test_drop_multiple_tables() {
362 let mut db = Database::new();
363
364 for name in &["table1", "table2", "table3"] {
366 let create_stmt = CreateTableStmt {
367 temporary: false,
368 if_not_exists: false,
369 table_name: name.to_string(),
370 columns: vec![ColumnDef {
371 name: "id".to_string(),
372 data_type: DataType::Integer,
373 nullable: false,
374 constraints: vec![],
375 default_value: None,
376 comment: None,
377 generated_expr: None,
378 is_exact_integer_type: false,
379 }],
380 table_constraints: vec![],
381 table_options: vec![],
382 quoted: false,
383 name_source: None,
384 as_query: None,
385 without_rowid: false,
386 };
387 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
388 }
389
390 assert_eq!(db.list_tables().len(), 3);
391
392 for name in &["table1", "table2", "table3"] {
394 let drop_stmt =
395 DropTableStmt { table_name: name.to_string(), if_exists: false, quoted: false };
396 let result = DropTableExecutor::execute(&drop_stmt, &mut db);
397 assert!(result.is_ok());
398 }
399
400 assert_eq!(db.list_tables().len(), 0);
401 }
402
403 #[test]
404 fn test_drop_table_case_sensitivity() {
405 let mut db = Database::new();
406
407 let create_stmt = CreateTableStmt {
409 temporary: false,
410 if_not_exists: false,
411 table_name: "MyTable".to_string(),
412 columns: vec![ColumnDef {
413 name: "id".to_string(),
414 data_type: DataType::Integer,
415 nullable: false,
416 constraints: vec![],
417 default_value: None,
418 comment: None,
419 generated_expr: None,
420 is_exact_integer_type: false,
421 }],
422 table_constraints: vec![],
423 table_options: vec![],
424 quoted: false,
425 name_source: None,
426 as_query: None,
427 without_rowid: false,
428 };
429 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
430
431 let drop_stmt =
433 DropTableStmt { table_name: "MyTable".to_string(), if_exists: false, quoted: false };
434 let result = DropTableExecutor::execute(&drop_stmt, &mut db);
435 assert!(result.is_ok());
436 }
437
438 #[test]
439 fn test_drop_table_cascades_to_indexes() {
440 use vibesql_ast::{CreateIndexStmt, IndexColumn, OrderDirection};
441
442 use crate::CreateIndexExecutor;
443
444 let mut db = Database::new();
445
446 let create_stmt = CreateTableStmt {
448 temporary: false,
449 if_not_exists: false,
450 table_name: "users".to_string(),
451 columns: vec![
452 ColumnDef {
453 name: "id".to_string(),
454 data_type: DataType::Integer,
455 nullable: false,
456 constraints: vec![],
457 default_value: None,
458 comment: None,
459 generated_expr: None,
460 is_exact_integer_type: false,
461 },
462 ColumnDef {
463 name: "email".to_string(),
464 data_type: DataType::Varchar { max_length: Some(255) },
465 nullable: false,
466 constraints: vec![],
467 default_value: None,
468 comment: None,
469 generated_expr: None,
470 is_exact_integer_type: false,
471 },
472 ],
473 table_constraints: vec![],
474 table_options: vec![],
475 quoted: false,
476 name_source: None,
477 as_query: None,
478 without_rowid: false,
479 };
480 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
481
482 let index1_stmt = CreateIndexStmt {
484 index_name: "idx_users_email".to_string(),
485 if_not_exists: false,
486 table_name: "users".to_string(),
487 index_type: vibesql_ast::IndexType::BTree { unique: false },
488 columns: vec![IndexColumn::Column {
489 column_name: "email".to_string(),
490 prefix_length: None,
491 direction: OrderDirection::Asc,
492 }],
493 where_clause: None,
494 };
495 CreateIndexExecutor::execute(&index1_stmt, &mut db).unwrap();
496
497 let index2_stmt = CreateIndexStmt {
498 index_name: "idx_users_id".to_string(),
499 if_not_exists: false,
500 table_name: "users".to_string(),
501 index_type: vibesql_ast::IndexType::BTree { unique: false },
502 columns: vec![IndexColumn::Column {
503 column_name: "id".to_string(),
504 prefix_length: None,
505 direction: OrderDirection::Asc,
506 }],
507 where_clause: None,
508 };
509 CreateIndexExecutor::execute(&index2_stmt, &mut db).unwrap();
510
511 assert!(db.index_exists("idx_users_email"));
513 assert!(db.index_exists("idx_users_id"));
514
515 let drop_stmt =
517 DropTableStmt { table_name: "users".to_string(), if_exists: false, quoted: false };
518 let result = DropTableExecutor::execute(&drop_stmt, &mut db);
519 assert!(result.is_ok());
520
521 assert!(!db.catalog.table_exists("users"));
523
524 assert!(!db.index_exists("idx_users_email"));
526 assert!(!db.index_exists("idx_users_id"));
527 }
528
529 #[test]
530 fn test_drop_and_recreate_table_with_same_index_names() {
531 use vibesql_ast::{CreateIndexStmt, IndexColumn, OrderDirection};
532
533 use crate::CreateIndexExecutor;
534
535 let mut db = Database::new();
536
537 let create_stmt = CreateTableStmt {
539 temporary: false,
540 if_not_exists: false,
541 table_name: "products".to_string(),
542 columns: vec![
543 ColumnDef {
544 name: "id".to_string(),
545 data_type: DataType::Integer,
546 nullable: false,
547 constraints: vec![],
548 default_value: None,
549 comment: None,
550 generated_expr: None,
551 is_exact_integer_type: false,
552 },
553 ColumnDef {
554 name: "name".to_string(),
555 data_type: DataType::Varchar { max_length: Some(100) },
556 nullable: false,
557 constraints: vec![],
558 default_value: None,
559 comment: None,
560 generated_expr: None,
561 is_exact_integer_type: false,
562 },
563 ],
564 table_constraints: vec![],
565 table_options: vec![],
566 quoted: false,
567 name_source: None,
568 as_query: None,
569 without_rowid: false,
570 };
571 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
572
573 let index_stmt = CreateIndexStmt {
575 index_name: "idx_products_name".to_string(),
576 if_not_exists: false,
577 table_name: "products".to_string(),
578 index_type: vibesql_ast::IndexType::BTree { unique: false },
579 columns: vec![IndexColumn::Column {
580 column_name: "name".to_string(),
581 prefix_length: None,
582 direction: OrderDirection::Asc,
583 }],
584 where_clause: None,
585 };
586 CreateIndexExecutor::execute(&index_stmt, &mut db).unwrap();
587
588 assert!(db.index_exists("idx_products_name"));
590
591 let drop_stmt =
593 DropTableStmt { table_name: "products".to_string(), if_exists: false, quoted: false };
594 DropTableExecutor::execute(&drop_stmt, &mut db).unwrap();
595
596 assert!(!db.catalog.table_exists("products"));
598 assert!(!db.index_exists("idx_products_name"));
599
600 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
602
603 let result = CreateIndexExecutor::execute(&index_stmt, &mut db);
605 assert!(result.is_ok(), "Should be able to recreate index with same name after table drop");
606 assert!(db.index_exists("idx_products_name"));
607 }
608
609 #[test]
610 fn test_drop_table_without_indexes() {
611 let mut db = Database::new();
612
613 let create_stmt = CreateTableStmt {
615 temporary: false,
616 if_not_exists: false,
617 table_name: "simple_table".to_string(),
618 columns: vec![ColumnDef {
619 name: "id".to_string(),
620 data_type: DataType::Integer,
621 nullable: false,
622 constraints: vec![],
623 default_value: None,
624 comment: None,
625 generated_expr: None,
626 is_exact_integer_type: false,
627 }],
628 table_constraints: vec![],
629 table_options: vec![],
630 quoted: false,
631 name_source: None,
632 as_query: None,
633 without_rowid: false,
634 };
635 CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
636
637 let drop_stmt = DropTableStmt {
639 table_name: "simple_table".to_string(),
640 if_exists: false,
641 quoted: false,
642 };
643 let result = DropTableExecutor::execute(&drop_stmt, &mut db);
644 assert!(result.is_ok(), "Dropping table without indexes should still work");
645 assert!(!db.catalog.table_exists("simple_table"));
646 }
647}