mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-19 09:34:18 +01:00
181 lines
7.0 KiB
Rust
181 lines
7.0 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 expr;
|
|
pub(crate) mod insert;
|
|
pub(crate) mod select;
|
|
pub(crate) mod where_clause;
|
|
|
|
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
use crate::pager::Pager;
|
|
use crate::schema::Schema;
|
|
use crate::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE};
|
|
use crate::util::normalize_ident;
|
|
use crate::vdbe::{builder::ProgramBuilder, Insn, Program};
|
|
use crate::{bail_parse_error, Result};
|
|
use insert::translate_insert;
|
|
use select::{prepare_select, translate_select};
|
|
use sqlite3_parser::ast;
|
|
|
|
/// Translate SQL statement into bytecode program.
|
|
pub fn translate(
|
|
schema: &Schema,
|
|
stmt: ast::Stmt,
|
|
database_header: Rc<RefCell<DatabaseHeader>>,
|
|
pager: Rc<Pager>,
|
|
) -> Result<Program> {
|
|
match stmt {
|
|
ast::Stmt::AlterTable(_, _) => bail_parse_error!("ALTER TABLE not supported yet"),
|
|
ast::Stmt::Analyze(_) => bail_parse_error!("ANALYZE not supported yet"),
|
|
ast::Stmt::Attach { .. } => bail_parse_error!("ATTACH not supported yet"),
|
|
ast::Stmt::Begin(_, _) => bail_parse_error!("BEGIN not supported yet"),
|
|
ast::Stmt::Commit(_) => bail_parse_error!("COMMIT not supported yet"),
|
|
ast::Stmt::CreateIndex { .. } => bail_parse_error!("CREATE INDEX not supported yet"),
|
|
ast::Stmt::CreateTable { .. } => bail_parse_error!("CREATE TABLE not supported yet"),
|
|
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 { .. } => {
|
|
bail_parse_error!("CREATE VIRTUAL TABLE not supported yet")
|
|
}
|
|
ast::Stmt::Delete { .. } => bail_parse_error!("DELETE not supported yet"),
|
|
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::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) => translate_pragma(&name, body, database_header, 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) => {
|
|
let select = prepare_select(schema, &select)?;
|
|
translate_select(select, database_header)
|
|
}
|
|
ast::Stmt::Update { .. } => bail_parse_error!("UPDATE not supported yet"),
|
|
ast::Stmt::Vacuum(_, _) => bail_parse_error!("VACUUM not supported yet"),
|
|
ast::Stmt::Insert {
|
|
with,
|
|
or_conflict,
|
|
tbl_name,
|
|
columns,
|
|
body,
|
|
returning,
|
|
} => translate_insert(
|
|
schema,
|
|
&with,
|
|
&or_conflict,
|
|
&tbl_name,
|
|
&columns,
|
|
&body,
|
|
&returning,
|
|
database_header,
|
|
),
|
|
}
|
|
}
|
|
|
|
fn translate_pragma(
|
|
name: &ast::QualifiedName,
|
|
body: Option<ast::PragmaBody>,
|
|
database_header: Rc<RefCell<DatabaseHeader>>,
|
|
pager: Rc<Pager>,
|
|
) -> Result<Program> {
|
|
let mut program = ProgramBuilder::new();
|
|
let init_label = program.allocate_label();
|
|
program.emit_insn_with_label_dependency(
|
|
Insn::Init {
|
|
target_pc: init_label,
|
|
},
|
|
init_label,
|
|
);
|
|
let start_offset = program.offset();
|
|
match body {
|
|
None => {
|
|
let pragma_result = program.alloc_register();
|
|
|
|
program.emit_insn(Insn::Integer {
|
|
value: database_header.borrow().default_cache_size.into(),
|
|
dest: pragma_result,
|
|
});
|
|
|
|
let pragma_result_end = program.next_free_register();
|
|
program.emit_insn(Insn::ResultRow {
|
|
start_reg: pragma_result,
|
|
count: pragma_result_end - pragma_result,
|
|
});
|
|
}
|
|
Some(ast::PragmaBody::Equals(value)) => {
|
|
let value_to_update = match value {
|
|
ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => {
|
|
numeric_value.parse::<i64>().unwrap()
|
|
}
|
|
ast::Expr::Unary(ast::UnaryOperator::Negative, expr) => match *expr {
|
|
ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => {
|
|
-numeric_value.parse::<i64>().unwrap()
|
|
}
|
|
_ => 0,
|
|
},
|
|
_ => 0,
|
|
};
|
|
update_pragma(
|
|
&name.name.0,
|
|
value_to_update,
|
|
database_header.clone(),
|
|
pager,
|
|
);
|
|
}
|
|
Some(ast::PragmaBody::Call(_)) => {
|
|
todo!()
|
|
}
|
|
};
|
|
program.emit_insn(Insn::Halt);
|
|
program.resolve_label(init_label, program.offset());
|
|
program.emit_insn(Insn::Transaction);
|
|
program.emit_constant_insns();
|
|
program.emit_insn(Insn::Goto {
|
|
target_pc: start_offset,
|
|
});
|
|
program.resolve_deferred_labels();
|
|
Ok(program.build(database_header))
|
|
}
|
|
|
|
fn update_pragma(name: &str, value: i64, header: Rc<RefCell<DatabaseHeader>>, pager: Rc<Pager>) {
|
|
match name {
|
|
"cache_size" => {
|
|
let mut cache_size_unformatted = value;
|
|
let mut cache_size = if cache_size_unformatted < 0 {
|
|
let kb = cache_size_unformatted.abs() * 1024;
|
|
kb / 512 // assume 512 page size for now
|
|
} else {
|
|
value
|
|
} as usize;
|
|
if cache_size < MIN_PAGE_CACHE_SIZE {
|
|
// update both in memory and stored disk value
|
|
cache_size = MIN_PAGE_CACHE_SIZE;
|
|
cache_size_unformatted = MIN_PAGE_CACHE_SIZE as i64;
|
|
}
|
|
|
|
// update in-memory header
|
|
header.borrow_mut().default_cache_size = cache_size_unformatted
|
|
.try_into()
|
|
.unwrap_or_else(|_| panic!("invalid value, too big for a i32 {}", value));
|
|
|
|
// update in disk
|
|
let header_copy = header.borrow().clone();
|
|
pager.write_database_header(&header_copy);
|
|
|
|
// update cache size
|
|
pager.change_page_cache_size(cache_size);
|
|
}
|
|
_ => todo!(),
|
|
}
|
|
}
|