diff --git a/core/lib.rs b/core/lib.rs index 94853aa80..f52c24bef 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -311,8 +311,10 @@ impl Connection { pub(crate) fn run_cmd(self: &Rc, cmd: Cmd) -> Result> { let db = self.db.clone(); let syms: &SymbolTable = &db.syms.borrow(); + println!("the command {:?}", cmd); match cmd { Cmd::Stmt(stmt) => { + println!("AST: {:?}", stmt); let program = Rc::new(translate::translate( &self.schema.borrow(), stmt, @@ -322,6 +324,7 @@ impl Connection { syms, QueryMode::Normal, )?); + println!("the generated program {:?}", program); let stmt = Statement::new(program, self.pager.clone()); Ok(Some(stmt)) } @@ -361,11 +364,13 @@ impl Connection { } pub fn execute(self: &Rc, sql: impl AsRef) -> Result<()> { + println!("running execute"); let sql = sql.as_ref(); let db = &self.db; let syms: &SymbolTable = &db.syms.borrow(); let mut parser = Parser::new(sql.as_bytes()); let cmd = parser.next()?; + println!("the command {:?}", cmd); if let Some(cmd) = cmd { match cmd { Cmd::Explain(stmt) => { diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 4a963581b..8f7da8e44 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -12,6 +12,7 @@ use crate::{LimboError, Result}; use std::cell::{Ref, RefCell}; use std::pin::Pin; use std::rc::Rc; +use std::sync::Arc; use super::pager::PageRef; use super::sqlite3_ondisk::{ @@ -2231,6 +2232,111 @@ impl BTreeCursor { Ok(Some(n_overflow)) } + + pub fn btree_drop(&mut self) -> Result> { + self.move_to_root(); + + loop { + let page = self.stack.top(); + return_if_locked!(page); + + if !page.is_loaded() { + self.pager.load_page(Arc::clone(&page))?; + return Ok(CursorResult::IO); + } + + let contents = page.get().contents.as_ref().unwrap(); + // TOOD: Uncomment after Krishvishal's PR https://github.com/tursodatabase/limbo/pull/785 merged + // let current_page_id = page.get().id; + + if !contents.is_leaf() { + let mut has_unprocessed_children = false; + + // Process all the regular cells first + for cell_idx in 0..contents.cell_count() { + let cell = contents.cell_get( + cell_idx, + Rc::clone(&self.pager), + self.payload_overflow_threshold_max(contents.page_type()), + self.payload_overflow_threshold_min(contents.page_type()), + self.usable_space(), + )?; + if let BTreeCell::TableInteriorCell(interior) = cell { + let child_page = + self.pager.read_page(interior._left_child_page as usize)?; + self.stack.push(child_page); + has_unprocessed_children = true; + break; + } + } + + if !has_unprocessed_children { + if let Some(rightmost) = contents.rightmost_pointer() { + let rightmost_page = self.pager.read_page(rightmost as usize)?; + self.stack.push(rightmost_page); + continue; + } + } + + if has_unprocessed_children { + continue; + } + } else { + for cell_idx in 0..contents.cell_count() { + let cell = contents.cell_get( + cell_idx, + Rc::clone(&self.pager), + self.payload_overflow_threshold_max(contents.page_type()), + self.payload_overflow_threshold_min(contents.page_type()), + self.usable_space(), + )?; + if let BTreeCell::TableLeafCell(TableLeafCell { + _rowid, + _payload, + first_overflow_page: Some(overflow_page_id), + }) = cell + { + let mut current_overflow_id = overflow_page_id; + loop { + let overflow_page = + self.pager.read_page(current_overflow_id as usize)?; + return_if_locked!(overflow_page); + + if !overflow_page.is_loaded() { + self.pager.load_page(Arc::clone(&overflow_page))?; + return Ok(CursorResult::IO); + } + + let overflow_contents = overflow_page.get().contents.as_ref().unwrap(); + let next_overflow_id = u32::from_be_bytes( + overflow_contents.as_ptr()[..4].try_into().unwrap(), + ); + + // TODO: Uncomment after Krishvishal's PR https://github.com/tursodatabase/limbo/pull/785 is merged + // self.pager + // .free_page(Some(overflow_page), current_overflow_id as usize)?; + + if next_overflow_id == 0 { + break; + } + current_overflow_id = next_overflow_id; + } + } + } + } + + // All children & overflow pages have been processed + // TODO: Uncomment after Krishvishal's PR https://github.com/tursodatabase/limbo/pull/785 is merged + // self.pager.free_page(Some(page), current_page_id)?; + + if self.stack.has_parent() { + self.stack.pop(); + } else { + break; + } + } + Ok(CursorResult::Ok(())) + } } impl PageStack { diff --git a/core/translate/mod.rs b/core/translate/mod.rs index 79791866b..6ee8497b8 100644 --- a/core/translate/mod.rs +++ b/core/translate/mod.rs @@ -85,7 +85,13 @@ pub fn translate( } ast::Stmt::Detach(_) => bail_parse_error!("DETACH not supported yet"), ast::Stmt::DropIndex { .. } => bail_parse_error!("DROP INDEX not supported yet"), - ast::Stmt::DropTable { .. } => bail_parse_error!("DROP TABLE not supported yet"), + // ast::Stmt::DropTable { .. } => bail_parse_error!("DROP TABLE not supported yet"), + ast::Stmt::DropTable { + if_exists, + tbl_name, + } => { + translate_drop_table(&mut program, tbl_name, if_exists, schema)?; + } ast::Stmt::DropTrigger { .. } => bail_parse_error!("DROP TRIGGER not supported yet"), ast::Stmt::DropView { .. } => bail_parse_error!("DROP VIEW not supported yet"), ast::Stmt::Pragma(name, body) => pragma::translate_pragma( @@ -510,6 +516,224 @@ fn translate_create_table( Ok(program) } +fn translate_drop_table( + program: &mut ProgramBuilder, + tbl_name: ast::QualifiedName, + if_exists: bool, + schema: &Schema, +) -> Result<()> { + let table = schema.get_table(tbl_name.name.0.as_str()); + if table.is_none() { + if if_exists { + let init_label = program.emit_init(); + let start_offset = program.offset(); + program.emit_halt(); + program.resolve_label(init_label, program.offset()); + program.emit_transaction(true); + program.emit_constant_insns(); + program.emit_goto(start_offset); + + return Ok(()); + } + bail_parse_error!("No such table: {}", tbl_name.name.0.as_str()); + } + let table = table.unwrap(); // safe to do since we have a check before this + + let init_label = program.emit_init(); + let start_offset = program.offset(); + + // 1. Drop the table BTree + program.emit_insn(Insn::DropBtree { + db: 0, + root: table.root_page, + }); + + // TODO: Drop indexes? + + // 2. Delete table metadata from sqlite_schema + let table_name = "sqlite_schema"; + let table = schema.get_table(&table_name).unwrap(); + let sqlite_schema_cursor_id = program.alloc_cursor_id( + Some(table_name.to_string()), + CursorType::BTreeTable(table.clone()), + ); + program.emit_insn(Insn::OpenWriteAsync { + cursor_id: sqlite_schema_cursor_id, + root_page: 1, + }); + program.emit_insn(Insn::OpenWriteAwait {}); + + // Rewind to the very beginning of the cursor + program.emit_insn(Insn::RewindAsync { + cursor_id: sqlite_schema_cursor_id, + }); + let end_metadata_label = program.allocate_label(); + program.emit_insn(Insn::RewindAwait { + cursor_id: sqlite_schema_cursor_id, + pc_if_empty: end_metadata_label, + }); + let metadata_loop = program.allocate_label(); + program.resolve_label(metadata_loop, program.offset()); + + // Load row details + let tbl_name_reg = program.alloc_register(); + program.emit_insn(Insn::Column { + cursor_id: sqlite_schema_cursor_id, + column: 2, + dest: tbl_name_reg, + }); + let string_reg = program.emit_string8_new_reg(tbl_name.name.0.clone()); + let next_label = program.allocate_label(); + program.emit_insn(Insn::Ne { + lhs: tbl_name_reg, + rhs: string_reg, + target_pc: next_label, + flags: CmpInsFlags::default(), + }); + + // Delete matching row + program.emit_insn(Insn::DeleteAsync { + cursor_id: sqlite_schema_cursor_id, + }); + program.emit_insn(Insn::DeleteAwait { + cursor_id: sqlite_schema_cursor_id, + }); + + // Move to next row + program.resolve_label(next_label, program.offset()); + program.emit_insn(Insn::NextAsync { + cursor_id: sqlite_schema_cursor_id, + }); + program.emit_insn(Insn::NextAwait { + cursor_id: sqlite_schema_cursor_id, + pc_if_next: metadata_loop, + }); + program.resolve_label(end_metadata_label, program.offset()); + + // Update schema + let parse_schema_where_clause = format!("tbl_name = {}", tbl_name.name.0); + program.emit_insn(Insn::ParseSchema { + db: 0, + where_clause: parse_schema_where_clause, + }); + + program.emit_halt(); + program.resolve_label(init_label, program.offset()); + program.emit_transaction(true); + program.emit_goto(start_offset); + + Ok(program) +} + +fn translate_drop_table( + program: &mut ProgramBuilder, + tbl_name: ast::QualifiedName, + if_exists: bool, + schema: &Schema, +) -> Result<()> { + let table = schema.get_table(tbl_name.name.0.as_str()); + if table.is_none() { + if if_exists { + let init_label = program.emit_init(); + let start_offset = program.offset(); + program.emit_halt(); + program.resolve_label(init_label, program.offset()); + program.emit_transaction(true); + program.emit_constant_insns(); + program.emit_goto(start_offset); + + return Ok(()); + } + bail_parse_error!("No such table: {}", tbl_name.name.0.as_str()); + } + let table = table.unwrap(); // safe to do since we have a check before this + + let init_label = program.emit_init(); + let start_offset = program.offset(); + + // 1. Drop the table BTree + program.emit_insn(Insn::DropBtree { + db: 0, + root: table.root_page, + }); + + // TODO: Drop indexes? + + // 2. Delete table metadata from sqlite_schema + let table_name = "sqlite_schema"; + let table = schema.get_table(&table_name).unwrap(); + let sqlite_schema_cursor_id = program.alloc_cursor_id( + Some(table_name.to_string()), + CursorType::BTreeTable(table.clone()), + ); + program.emit_insn(Insn::OpenWriteAsync { + cursor_id: sqlite_schema_cursor_id, + root_page: 1, + }); + program.emit_insn(Insn::OpenWriteAwait {}); + + // Rewind to the very beginning of the cursor + program.emit_insn(Insn::RewindAsync { + cursor_id: sqlite_schema_cursor_id, + }); + let end_metadata_label = program.allocate_label(); + program.emit_insn(Insn::RewindAwait { + cursor_id: sqlite_schema_cursor_id, + pc_if_empty: end_metadata_label, + }); + let metadata_loop = program.allocate_label(); + program.resolve_label(metadata_loop, program.offset()); + + // Load row details + let tbl_name_reg = program.alloc_register(); + program.emit_insn(Insn::Column { + cursor_id: sqlite_schema_cursor_id, + column: 2, + dest: tbl_name_reg, + }); + let string_reg = program.emit_string8_new_reg(tbl_name.name.0.clone()); + let next_label = program.allocate_label(); + program.emit_insn(Insn::Ne { + lhs: tbl_name_reg, + rhs: string_reg, + target_pc: next_label, + flags: CmpInsFlags::default(), + }); + + // Delete matching row + program.emit_insn(Insn::DeleteAsync { + cursor_id: sqlite_schema_cursor_id, + }); + program.emit_insn(Insn::DeleteAwait { + cursor_id: sqlite_schema_cursor_id, + }); + + // Move to next row + program.resolve_label(next_label, program.offset()); + program.emit_insn(Insn::NextAsync { + cursor_id: sqlite_schema_cursor_id, + }); + program.emit_insn(Insn::NextAwait { + cursor_id: sqlite_schema_cursor_id, + pc_if_next: metadata_loop, + }); + program.resolve_label(end_metadata_label, program.offset()); + + // Update schema + let parse_schema_where_clause = format!("tbl_name = {}", tbl_name.name.0); + program.emit_insn(Insn::ParseSchema { + db: 0, + where_clause: parse_schema_where_clause, + }); + + program.emit_halt(); + program.resolve_label(init_label, program.offset()); + program.emit_transaction(true); + program.emit_goto(start_offset); + + Ok(()) +} + enum PrimaryKeyDefinitionType<'a> { Simple { typename: Option<&'a str>, diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 6f55d5dc1..1b11cb2e1 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1077,6 +1077,15 @@ pub fn insn_to_str( 0, format!("r[{}]=root iDb={} flags={}", root, db, flags), ), + Insn::DropBtree { db, root } => ( + "DropBtree", + *db as i32, + *root as i32, + 0, + OwnedValue::build_text(Rc::new("".to_string())), + 0, + format!("root iDb={}", db), + ), Insn::Close { cursor_id } => ( "Close", *cursor_id as i32, diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 2d673b018..036f607f8 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -567,6 +567,14 @@ pub enum Insn { flags: usize, }, + // Drop a b-tree + DropBtree { + // The database within which this b-tree needs to be dropped (P1). + db: usize, + // The root page of this b-tree (P2). + root: usize, + }, + /// Close a cursor. Close { cursor_id: CursorID, diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 3ccfdc1e3..efdc34184 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2634,6 +2634,14 @@ impl Program { state.registers[*root] = OwnedValue::Integer(root_page as i64); state.pc += 1; } + Insn::DropBtree { db, root } => { + if *db > 0 { + todo!("temp databases not implemented yet"); + } + let mut cursor = Box::new(BTreeCursor::new(pager.clone(), *root)); + cursor.btree_drop()?; + state.pc += 1; + } Insn::Close { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); cursors.get_mut(*cursor_id).unwrap().take();