From 6c8eef2face8d10c1da9fb360efe5005501b058d Mon Sep 17 00:00:00 2001 From: Anton Harniakou Date: Fri, 25 Apr 2025 21:41:02 +0300 Subject: [PATCH] Support DROP INDEX This commit adds suport for DROP INDEX. Bytecode produced by this commit differs from SQLITE's bytecode, main reason we don't do autovacuum or repacking of pages like SQLITE does. --- core/schema.rs | 8 ++ core/translate/index.rs | 175 ++++++++++++++++++++++++++++++++++++++++ core/translate/mod.rs | 7 +- core/vdbe/execute.rs | 26 ++++-- core/vdbe/explain.rs | 9 +++ core/vdbe/insn.rs | 10 ++- 6 files changed, 225 insertions(+), 10 deletions(-) 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,