diff --git a/core/schema.rs b/core/schema.rs index 42c619693..648eb41c6 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -93,6 +93,14 @@ impl Schema { let name = normalize_ident(table_name); self.indexes.remove(&name); } + + pub fn remove_index(&mut self, idx: &Index) { + let name = normalize_ident(&idx.table_name); + self.indexes + .get_mut(&name) + .expect("Must have the index") + .retain_mut(|other_idx| other_idx.name != idx.name); + } } #[derive(Clone, Debug)] diff --git a/core/translate/index.rs b/core/translate/index.rs index 55222e40f..74841a49d 100644 --- a/core/translate/index.rs +++ b/core/translate/index.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use crate::vdbe::insn::CmpInsFlags; use crate::{ schema::{BTreeTable, Column, Index, IndexColumn, PseudoTable, Schema}, storage::pager::CreateBTreeFlags, @@ -308,3 +309,177 @@ fn create_idx_stmt_to_sql( sql.push(')'); sql } + +pub fn translate_drop_index( + mode: QueryMode, + idx_name: &str, + if_exists: bool, + schema: &Schema, +) -> crate::Result { + let idx_name = normalize_ident(idx_name); + let mut program = ProgramBuilder::new(crate::vdbe::builder::ProgramBuilderOpts { + query_mode: mode, + num_cursors: 5, + approx_num_insns: 40, + approx_num_labels: 5, + }); + + // Find the index in Schema + let mut maybe_index = None; + for val in schema.indexes.values() { + if maybe_index.is_some() { + break; + } + for idx in val { + if idx.name == idx_name { + maybe_index = Some(idx); + break; + } + } + } + + // If there's no index if_exist is true, + // then return normaly, otherwise show an error. + if maybe_index.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(program); + } else { + return Err(crate::error::LimboError::InvalidArgument(format!( + "No such index: {}", + &idx_name + ))); + } + } + + // 1. Init + // 2. Goto + let init_label = program.emit_init(); + let start_offset = program.offset(); + + // According to sqlite should emit Null instruction + // but why? + let null_reg = program.alloc_register(); + program.emit_null(null_reg, None); + + // String8; r[3] = 'some idx name' + let index_name_reg = program.emit_string8_new_reg(idx_name.to_string()); + // String8; r[4] = 'index' + let index_str_reg = program.emit_string8_new_reg("index".to_string()); + + // for r[5]=rowid + let row_id_reg = program.alloc_register(); + + // We're going to use this cursor to search through sqlite_schema + let sqlite_table = schema.get_btree_table(SQLITE_TABLEID).unwrap(); + let sqlite_schema_cursor_id = program.alloc_cursor_id( + Some(SQLITE_TABLEID.to_owned()), + CursorType::BTreeTable(sqlite_table.clone()), + ); + + // Open root=1 iDb=0; sqlite_schema for writing + program.emit_insn(Insn::OpenWrite { + cursor_id: sqlite_schema_cursor_id, + root_page: RegisterOrLiteral::Literal(sqlite_table.root_page), + }); + + let loop_start_label = program.allocate_label(); + let loop_end_label = program.allocate_label(); + program.emit_insn(Insn::Rewind { + cursor_id: sqlite_schema_cursor_id, + pc_if_empty: loop_end_label, + }); + program.resolve_label(loop_start_label, program.offset()); + + // Read sqlite_schema.name into dest_reg + let dest_reg = program.alloc_register(); + program.emit_insn(Insn::Column { + cursor_id: sqlite_schema_cursor_id, + column: 1, // sqlite_schema.name + dest: dest_reg, + }); + + // if current column is not index_name then jump to Next + // skip if sqlite_schema.name != index_name_reg + let next_label = program.allocate_label(); + program.emit_insn(Insn::Ne { + lhs: index_name_reg, + rhs: dest_reg, + target_pc: next_label, + flags: CmpInsFlags::default(), + }); + + // read type of table + // skip if sqlite_schema.type != 'index' (index_str_reg) + program.emit_insn(Insn::Column { + cursor_id: sqlite_schema_cursor_id, + column: 0, + dest: dest_reg, + }); + // if current column is not index then jump to Next + program.emit_insn(Insn::Ne { + lhs: index_str_reg, + rhs: dest_reg, + target_pc: next_label, + flags: CmpInsFlags::default(), + }); + + program.emit_insn(Insn::RowId { + cursor_id: sqlite_schema_cursor_id, + dest: row_id_reg, + }); + + let label_once_end = program.allocate_label(); + program.emit_insn(Insn::Once { + target_pc_when_reentered: label_once_end, + }); + program.resolve_label(label_once_end, program.offset()); + + program.emit_insn(Insn::Delete { + cursor_id: sqlite_schema_cursor_id, + }); + + program.resolve_label(next_label, program.offset()); + program.emit_insn(Insn::Next { + cursor_id: sqlite_schema_cursor_id, + pc_if_next: loop_start_label, + }); + + program.resolve_label(loop_end_label, program.offset()); + + /* TODO do set cookie for real + program.emit_insn(Insn::SetCookie { + ... + }); + */ + + // Destroy index btree + program.emit_insn(Insn::Destroy { + root: maybe_index.unwrap().root_page, + former_root_reg: 0, + is_temp: 0, + }); + + // Remove from the Schema any mention of the index + if let Some(idx) = maybe_index { + program.emit_insn(Insn::DropIndex { + index: idx.clone(), + db: 0, + }); + } + + // Epilogue: + program.emit_halt(); + program.resolve_label(init_label, program.offset()); + program.emit_transaction(true); + program.emit_constant_insns(); + program.emit_goto(start_offset); + + Ok(program) +} diff --git a/core/translate/mod.rs b/core/translate/mod.rs index cf93b34ba..53414f971 100644 --- a/core/translate/mod.rs +++ b/core/translate/mod.rs @@ -35,7 +35,7 @@ use crate::translate::delete::translate_delete; use crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts, QueryMode}; use crate::vdbe::Program; use crate::{bail_parse_error, Connection, Result, SymbolTable}; -use index::translate_create_index; +use index::{translate_create_index, translate_drop_index}; use insert::translate_insert; use limbo_sqlite3_parser::ast::{self, Delete, Insert}; use schema::{translate_create_table, translate_create_virtual_table, translate_drop_table}; @@ -110,7 +110,10 @@ pub fn translate( translate_delete(query_mode, schema, &tbl_name, where_clause, limit, syms)? } ast::Stmt::Detach(_) => bail_parse_error!("DETACH not supported yet"), - ast::Stmt::DropIndex { .. } => bail_parse_error!("DROP INDEX not supported yet"), + ast::Stmt::DropIndex { + if_exists, + idx_name, + } => translate_drop_index(query_mode, &idx_name.name.0, if_exists, schema)?, ast::Stmt::DropTable { if_exists, tbl_name, diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 928f7f94a..e4ca972e5 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -189,6 +189,24 @@ pub fn op_divide( Ok(InsnFunctionStepResult::Step) } +pub fn op_drop_index( + program: &Program, + state: &mut ProgramState, + insn: &Insn, + pager: &Rc, + mv_store: Option<&Rc>, +) -> Result { + let Insn::DropIndex { index, db } = insn else { + unreachable!("unexpected Insn {:?}", insn) + }; + if let Some(conn) = program.connection.upgrade() { + let mut schema = conn.schema.write(); + schema.remove_index(&index); + } + state.pc += 1; + Ok(InsnFunctionStepResult::Step) +} + pub fn op_remainder( program: &Program, state: &mut ProgramState, @@ -4157,13 +4175,7 @@ pub fn op_drop_table( pager: &Rc, mv_store: Option<&Rc>, ) -> Result { - let Insn::DropTable { - db, - _p2, - _p3, - table_name, - } = insn - else { + let Insn::DropTable { db, table_name, .. } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if *db > 0 { diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index eadb5a0d9..10966c5f1 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1179,6 +1179,15 @@ pub fn insn_to_str( 0, format!("DROP TABLE {}", table_name), ), + Insn::DropIndex { db, index } => ( + "DropIndex", + 0, + 0, + 0, + OwnedValue::build_text(&Rc::new(index.name.clone())), + 0, + format!("DROP INDEX {}", index.name), + ), Insn::Close { cursor_id } => ( "Close", *cursor_id as i32, diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 6f310f746..9a24626b9 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -1,11 +1,12 @@ use std::{ num::{NonZero, NonZeroUsize}, rc::Rc, + sync::Arc, }; use super::{execute, AggFunc, BranchOffset, CursorID, FuncCtx, InsnFunction, PageIdx}; use crate::{ - schema::BTreeTable, + schema::{BTreeTable, Index}, storage::{pager::CreateBTreeFlags, wal::CheckpointMode}, types::Record, }; @@ -730,6 +731,12 @@ pub enum Insn { // The name of the table being dropped table_name: String, }, + DropIndex { + /// The database within which this index needs to be dropped (P1). + db: usize, + // The name of the index being dropped + index: Arc, + }, /// Close a cursor. Close { @@ -856,6 +863,7 @@ impl Insn { Insn::Subtract { .. } => execute::op_subtract, Insn::Multiply { .. } => execute::op_multiply, Insn::Divide { .. } => execute::op_divide, + Insn::DropIndex { .. } => execute::op_drop_index, Insn::Compare { .. } => execute::op_compare, Insn::BitAnd { .. } => execute::op_bit_and, Insn::BitOr { .. } => execute::op_bit_or,