mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-07 09:14:26 +01:00
drop table: addresses issue https://github.com/tursodatabase/limbo/issues/894 which requires DROP TABLE to be implemented
this is the initial commit is for the implementation of DROP TABLE. It adds support for the DROP TABLE instruction and adds a DropBTree instruction. It also implements the btree_drop method in btree.rs which makes use of free_page method which will be implemented via PR https://github.com/tursodatabase/limbo/pull/785
This commit is contained in:
@@ -316,8 +316,10 @@ impl Connection {
|
||||
pub(crate) fn run_cmd(self: &Rc<Connection>, cmd: Cmd) -> Result<Option<Statement>> {
|
||||
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,
|
||||
@@ -327,6 +329,7 @@ impl Connection {
|
||||
syms,
|
||||
QueryMode::Normal,
|
||||
)?);
|
||||
println!("the generated program {:?}", program);
|
||||
let stmt = Statement::new(program, self.pager.clone());
|
||||
Ok(Some(stmt))
|
||||
}
|
||||
@@ -367,11 +370,13 @@ impl Connection {
|
||||
}
|
||||
|
||||
pub fn execute(self: &Rc<Connection>, sql: impl AsRef<str>) -> 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) => {
|
||||
|
||||
@@ -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::{
|
||||
@@ -2234,6 +2235,111 @@ impl BTreeCursor {
|
||||
|
||||
Ok(Some(n_overflow))
|
||||
}
|
||||
|
||||
pub fn btree_drop(&mut self) -> Result<CursorResult<()>> {
|
||||
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 {
|
||||
|
||||
@@ -89,7 +89,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(
|
||||
@@ -515,6 +521,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>,
|
||||
|
||||
@@ -1101,6 +1101,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,
|
||||
|
||||
@@ -590,6 +590,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,
|
||||
|
||||
@@ -2688,6 +2688,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();
|
||||
|
||||
Reference in New Issue
Block a user