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.
This commit is contained in:
Anton Harniakou
2025-04-25 21:41:02 +03:00
parent 7137f4ab3b
commit 6c8eef2fac
6 changed files with 225 additions and 10 deletions

View File

@@ -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)]

View File

@@ -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<ProgramBuilder> {
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)
}

View File

@@ -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,

View File

@@ -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<Pager>,
mv_store: Option<&Rc<MvStore>>,
) -> Result<InsnFunctionStepResult> {
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<Pager>,
mv_store: Option<&Rc<MvStore>>,
) -> Result<InsnFunctionStepResult> {
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 {

View File

@@ -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,

View File

@@ -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<Index>,
},
/// 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,