Files
turso/core/translate/mod.rs
Pekka Enberg 14ef25ebb8 Merge 'Add drop index' from Anton Harniakou
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.
Closes #1280

Closes #1444
2025-05-10 08:04:39 +03:00

221 lines
7.8 KiB
Rust

//! The VDBE bytecode code generator.
//!
//! This module is responsible for translating the SQL AST into a sequence of
//! instructions for the VDBE. The VDBE is a register-based virtual machine that
//! executes bytecode instructions. This code generator is responsible for taking
//! the SQL AST and generating the corresponding VDBE instructions. For example,
//! a SELECT statement will be translated into a sequence of instructions that
//! will read rows from the database and filter them according to a WHERE clause.
pub(crate) mod aggregation;
pub(crate) mod delete;
pub(crate) mod emitter;
pub(crate) mod expr;
pub(crate) mod group_by;
pub(crate) mod index;
pub(crate) mod insert;
pub(crate) mod main_loop;
pub(crate) mod optimizer;
pub(crate) mod order_by;
pub(crate) mod plan;
pub(crate) mod planner;
pub(crate) mod pragma;
pub(crate) mod result_row;
pub(crate) mod schema;
pub(crate) mod select;
pub(crate) mod subquery;
pub(crate) mod transaction;
pub(crate) mod update;
use crate::fast_lock::SpinLock;
use crate::schema::Schema;
use crate::storage::pager::Pager;
use crate::storage::sqlite3_ondisk::DatabaseHeader;
use crate::translate::delete::translate_delete;
use crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts, QueryMode};
use crate::vdbe::Program;
use crate::{bail_parse_error, Connection, LimboError, Result, SymbolTable};
use fallible_iterator::FallibleIterator as _;
use index::{translate_create_index, translate_drop_index};
use insert::translate_insert;
use limbo_sqlite3_parser::ast::{self, Delete, Insert};
use limbo_sqlite3_parser::lexer::sql::Parser;
use schema::{
translate_create_table, translate_create_virtual_table, translate_drop_table, ParseSchema,
SQLITE_TABLEID,
};
use select::translate_select;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use transaction::{translate_tx_begin, translate_tx_commit};
use update::translate_update;
/// Translate SQL statement into bytecode program.
pub fn translate(
schema: &Schema,
stmt: ast::Stmt,
database_header: Arc<SpinLock<DatabaseHeader>>,
pager: Rc<Pager>,
connection: Weak<Connection>,
syms: &SymbolTable,
query_mode: QueryMode,
) -> Result<Program> {
let mut change_cnt_on = false;
let program = match stmt {
ast::Stmt::AlterTable(a) => {
let (table_name, alter_table) = a.as_ref();
match alter_table {
ast::AlterTableBody::RenameTo(name) => {
let rename = &name.0;
let name = &table_name.name.0;
let Some(table) = schema.tables.get(name) else {
return Err(LimboError::ParseError(format!("no such table: {name}")));
};
if schema.tables.contains_key(rename) {
return Err(LimboError::ParseError(format!(
"there is already another table or index with this name: {rename}"
)));
};
let Some(btree) = table.btree() else { todo!() };
let mut btree = (*btree).clone();
btree.name = rename.clone();
let sql = btree.to_sql();
let stmt = format!(
r#"
UPDATE {SQLITE_TABLEID}
SET name = '{rename}'
, tbl_name = '{rename}'
, sql = '{sql}'
WHERE tbl_name = '{name}'
"#,
);
let mut parser = Parser::new(stmt.as_bytes());
let Some(ast::Cmd::Stmt(ast::Stmt::Update(mut update))) = parser.next()? else {
unreachable!();
};
translate_update(
QueryMode::Normal,
schema,
&mut update,
syms,
ParseSchema::Reload,
)?
}
_ => todo!(),
}
}
ast::Stmt::Analyze(_) => bail_parse_error!("ANALYZE not supported yet"),
ast::Stmt::Attach { .. } => bail_parse_error!("ATTACH not supported yet"),
ast::Stmt::Begin(tx_type, tx_name) => translate_tx_begin(tx_type, tx_name)?,
ast::Stmt::Commit(tx_name) => translate_tx_commit(tx_name)?,
ast::Stmt::CreateIndex {
unique,
if_not_exists,
idx_name,
tbl_name,
columns,
..
} => {
change_cnt_on = true;
translate_create_index(
query_mode,
(unique, if_not_exists),
&idx_name.name.0,
&tbl_name.0,
&columns,
schema,
)?
}
ast::Stmt::CreateTable {
temporary,
if_not_exists,
tbl_name,
body,
} => translate_create_table(
query_mode,
tbl_name,
temporary,
*body,
if_not_exists,
schema,
)?,
ast::Stmt::CreateTrigger { .. } => bail_parse_error!("CREATE TRIGGER not supported yet"),
ast::Stmt::CreateView { .. } => bail_parse_error!("CREATE VIEW not supported yet"),
ast::Stmt::CreateVirtualTable(vtab) => {
translate_create_virtual_table(*vtab, schema, query_mode, &syms)?
}
ast::Stmt::Delete(delete) => {
let Delete {
tbl_name,
where_clause,
limit,
..
} = *delete;
change_cnt_on = true;
translate_delete(query_mode, schema, &tbl_name, where_clause, limit, syms)?
}
ast::Stmt::Detach(_) => bail_parse_error!("DETACH 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,
} => translate_drop_table(query_mode, 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(
query_mode,
schema,
&name,
body.map(|b| *b),
database_header.clone(),
pager,
)?,
ast::Stmt::Reindex { .. } => bail_parse_error!("REINDEX not supported yet"),
ast::Stmt::Release(_) => bail_parse_error!("RELEASE not supported yet"),
ast::Stmt::Rollback { .. } => bail_parse_error!("ROLLBACK not supported yet"),
ast::Stmt::Savepoint(_) => bail_parse_error!("SAVEPOINT not supported yet"),
ast::Stmt::Select(select) => translate_select(query_mode, schema, *select, syms)?,
ast::Stmt::Update(mut update) => {
translate_update(query_mode, schema, &mut update, syms, ParseSchema::None)?
}
ast::Stmt::Vacuum(_, _) => bail_parse_error!("VACUUM not supported yet"),
ast::Stmt::Insert(insert) => {
let Insert {
with,
or_conflict,
tbl_name,
columns,
body,
returning,
} = *insert;
change_cnt_on = true;
translate_insert(
query_mode,
schema,
&with,
&or_conflict,
&tbl_name,
&columns,
&body,
&returning,
syms,
)?
}
};
Ok(program.build(database_header, connection, change_cnt_on))
}