mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-06 17:54:20 +01:00
447 lines
14 KiB
Rust
447 lines
14 KiB
Rust
//! VDBE bytecode generation for pragma statements.
|
|
//! More info: https://www.sqlite.org/pragma.html.
|
|
|
|
use limbo_sqlite3_parser::ast::PragmaName;
|
|
use limbo_sqlite3_parser::ast::{self, Expr};
|
|
use std::rc::{Rc, Weak};
|
|
use std::sync::Arc;
|
|
|
|
use crate::fast_lock::SpinLock;
|
|
use crate::schema::Schema;
|
|
use crate::storage::pager::AutoVacuumMode;
|
|
use crate::storage::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE};
|
|
use crate::storage::wal::CheckpointMode;
|
|
use crate::util::{normalize_ident, parse_signed_number};
|
|
use crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts, QueryMode};
|
|
use crate::vdbe::insn::{Cookie, Insn};
|
|
use crate::{bail_parse_error, LimboError, Pager, Value};
|
|
use std::str::FromStr;
|
|
use strum::IntoEnumIterator;
|
|
|
|
fn list_pragmas(program: &mut ProgramBuilder) {
|
|
for x in PragmaName::iter() {
|
|
let register = program.emit_string8_new_reg(x.to_string());
|
|
program.emit_result_row(register, 1);
|
|
}
|
|
program.add_pragma_result_column("pragma_list".into());
|
|
program.epilogue(crate::translate::emitter::TransactionMode::None);
|
|
}
|
|
|
|
pub fn translate_pragma(
|
|
query_mode: QueryMode,
|
|
schema: &Schema,
|
|
name: &ast::QualifiedName,
|
|
body: Option<ast::PragmaBody>,
|
|
database_header: Arc<SpinLock<DatabaseHeader>>,
|
|
pager: Rc<Pager>,
|
|
connection: Weak<crate::Connection>,
|
|
mut program: ProgramBuilder,
|
|
) -> crate::Result<ProgramBuilder> {
|
|
let opts = ProgramBuilderOpts {
|
|
query_mode,
|
|
num_cursors: 0,
|
|
approx_num_insns: 20,
|
|
approx_num_labels: 0,
|
|
};
|
|
program.extend(&opts);
|
|
let mut write = false;
|
|
|
|
if name.name.0.eq_ignore_ascii_case("pragma_list") {
|
|
list_pragmas(&mut program);
|
|
return Ok(program);
|
|
}
|
|
|
|
let pragma = match PragmaName::from_str(&name.name.0) {
|
|
Ok(pragma) => pragma,
|
|
Err(_) => bail_parse_error!("Not a valid pragma name"),
|
|
};
|
|
|
|
match body {
|
|
None => {
|
|
query_pragma(
|
|
pragma,
|
|
schema,
|
|
None,
|
|
database_header.clone(),
|
|
pager,
|
|
connection,
|
|
&mut program,
|
|
)?;
|
|
}
|
|
Some(ast::PragmaBody::Equals(value)) => match pragma {
|
|
PragmaName::TableInfo => {
|
|
query_pragma(
|
|
pragma,
|
|
schema,
|
|
Some(value),
|
|
database_header.clone(),
|
|
pager,
|
|
connection,
|
|
&mut program,
|
|
)?;
|
|
}
|
|
_ => {
|
|
write = true;
|
|
update_pragma(
|
|
pragma,
|
|
schema,
|
|
value,
|
|
database_header.clone(),
|
|
pager,
|
|
connection,
|
|
&mut program,
|
|
)?;
|
|
}
|
|
},
|
|
Some(ast::PragmaBody::Call(value)) => match pragma {
|
|
PragmaName::TableInfo => {
|
|
query_pragma(
|
|
pragma,
|
|
schema,
|
|
Some(value),
|
|
database_header.clone(),
|
|
pager,
|
|
connection,
|
|
&mut program,
|
|
)?;
|
|
}
|
|
_ => {
|
|
todo!()
|
|
}
|
|
},
|
|
};
|
|
program.epilogue(match write {
|
|
false => super::emitter::TransactionMode::Read,
|
|
true => super::emitter::TransactionMode::Write,
|
|
});
|
|
|
|
Ok(program)
|
|
}
|
|
|
|
fn update_pragma(
|
|
pragma: PragmaName,
|
|
schema: &Schema,
|
|
value: ast::Expr,
|
|
header: Arc<SpinLock<DatabaseHeader>>,
|
|
pager: Rc<Pager>,
|
|
connection: Weak<crate::Connection>,
|
|
program: &mut ProgramBuilder,
|
|
) -> crate::Result<()> {
|
|
match pragma {
|
|
PragmaName::CacheSize => {
|
|
let cache_size = match parse_signed_number(&value)? {
|
|
Value::Integer(size) => size,
|
|
Value::Float(size) => size as i64,
|
|
_ => bail_parse_error!("Invalid value for cache size pragma"),
|
|
};
|
|
update_cache_size(cache_size, header, pager, connection)?;
|
|
Ok(())
|
|
}
|
|
PragmaName::JournalMode => {
|
|
query_pragma(
|
|
PragmaName::JournalMode,
|
|
schema,
|
|
None,
|
|
header,
|
|
pager,
|
|
connection,
|
|
program,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
PragmaName::LegacyFileFormat => Ok(()),
|
|
PragmaName::WalCheckpoint => {
|
|
query_pragma(
|
|
PragmaName::WalCheckpoint,
|
|
schema,
|
|
None,
|
|
header,
|
|
pager,
|
|
connection,
|
|
program,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
PragmaName::PageCount => {
|
|
query_pragma(
|
|
PragmaName::PageCount,
|
|
schema,
|
|
None,
|
|
header,
|
|
pager,
|
|
connection,
|
|
program,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
PragmaName::UserVersion => {
|
|
let data = parse_signed_number(&value)?;
|
|
let version_value = match data {
|
|
Value::Integer(i) => i as i32,
|
|
Value::Float(f) => f as i32,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
program.emit_insn(Insn::SetCookie {
|
|
db: 0,
|
|
cookie: Cookie::UserVersion,
|
|
value: version_value,
|
|
p5: 1,
|
|
});
|
|
Ok(())
|
|
}
|
|
PragmaName::SchemaVersion => {
|
|
// TODO: Implement updating schema_version
|
|
todo!("updating schema_version not yet implemented")
|
|
}
|
|
PragmaName::TableInfo => {
|
|
// because we need control over the write parameter for the transaction,
|
|
// this should be unreachable. We have to force-call query_pragma before
|
|
// getting here
|
|
unreachable!();
|
|
}
|
|
PragmaName::PageSize => {
|
|
todo!("updating page_size is not yet implemented")
|
|
}
|
|
PragmaName::AutoVacuum => {
|
|
let auto_vacuum_mode = match value {
|
|
Expr::Name(name) => {
|
|
let name = name.0.to_lowercase();
|
|
match name.as_str() {
|
|
"none" => 0,
|
|
"full" => 1,
|
|
"incremental" => 2,
|
|
_ => {
|
|
return Err(LimboError::InvalidArgument(
|
|
"invalid auto vacuum mode".to_string(),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(LimboError::InvalidArgument(
|
|
"invalid auto vacuum mode".to_string(),
|
|
))
|
|
}
|
|
};
|
|
match auto_vacuum_mode {
|
|
0 => update_auto_vacuum_mode(AutoVacuumMode::None, 0, header, pager)?,
|
|
1 => update_auto_vacuum_mode(AutoVacuumMode::Full, 1, header, pager)?,
|
|
2 => update_auto_vacuum_mode(AutoVacuumMode::Incremental, 1, header, pager)?,
|
|
_ => {
|
|
return Err(LimboError::InvalidArgument(
|
|
"invalid auto vacuum mode".to_string(),
|
|
))
|
|
}
|
|
}
|
|
let largest_root_page_number_reg = program.alloc_register();
|
|
program.emit_insn(Insn::ReadCookie {
|
|
db: 0,
|
|
dest: largest_root_page_number_reg,
|
|
cookie: Cookie::LargestRootPageNumber,
|
|
});
|
|
let set_cookie_label = program.allocate_label();
|
|
program.emit_insn(Insn::If {
|
|
reg: largest_root_page_number_reg,
|
|
target_pc: set_cookie_label,
|
|
jump_if_null: false,
|
|
});
|
|
program.emit_insn(Insn::Halt {
|
|
err_code: 0,
|
|
description: "Early halt because auto vacuum mode is not enabled".to_string(),
|
|
});
|
|
program.resolve_label(set_cookie_label, program.offset());
|
|
program.emit_insn(Insn::SetCookie {
|
|
db: 0,
|
|
cookie: Cookie::IncrementalVacuum,
|
|
value: auto_vacuum_mode - 1,
|
|
p5: 0,
|
|
});
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn query_pragma(
|
|
pragma: PragmaName,
|
|
schema: &Schema,
|
|
value: Option<ast::Expr>,
|
|
database_header: Arc<SpinLock<DatabaseHeader>>,
|
|
pager: Rc<Pager>,
|
|
connection: Weak<crate::Connection>,
|
|
program: &mut ProgramBuilder,
|
|
) -> crate::Result<()> {
|
|
let register = program.alloc_register();
|
|
match pragma {
|
|
PragmaName::CacheSize => {
|
|
program.emit_int(
|
|
connection.upgrade().unwrap().get_cache_size() as i64,
|
|
register,
|
|
);
|
|
program.emit_result_row(register, 1);
|
|
program.add_pragma_result_column(pragma.to_string());
|
|
}
|
|
PragmaName::JournalMode => {
|
|
program.emit_string8("wal".into(), register);
|
|
program.emit_result_row(register, 1);
|
|
program.add_pragma_result_column(pragma.to_string());
|
|
}
|
|
PragmaName::LegacyFileFormat => {}
|
|
PragmaName::WalCheckpoint => {
|
|
// Checkpoint uses 3 registers: P1, P2, P3. Ref Insn::Checkpoint for more info.
|
|
// Allocate two more here as one was allocated at the top.
|
|
program.alloc_register();
|
|
program.alloc_register();
|
|
program.emit_insn(Insn::Checkpoint {
|
|
database: 0,
|
|
checkpoint_mode: CheckpointMode::Passive,
|
|
dest: register,
|
|
});
|
|
program.emit_result_row(register, 3);
|
|
}
|
|
PragmaName::PageCount => {
|
|
program.emit_insn(Insn::PageCount {
|
|
db: 0,
|
|
dest: register,
|
|
});
|
|
program.emit_result_row(register, 1);
|
|
program.add_pragma_result_column(pragma.to_string());
|
|
}
|
|
PragmaName::TableInfo => {
|
|
let table = match value {
|
|
Some(ast::Expr::Name(name)) => {
|
|
let tbl = normalize_ident(&name.0);
|
|
schema.get_table(&tbl)
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
let base_reg = register;
|
|
program.alloc_register();
|
|
program.alloc_register();
|
|
program.alloc_register();
|
|
program.alloc_register();
|
|
program.alloc_register();
|
|
if let Some(table) = table {
|
|
for (i, column) in table.columns().iter().enumerate() {
|
|
// cid
|
|
program.emit_int(i as i64, base_reg);
|
|
// name
|
|
program.emit_string8(column.name.clone().unwrap_or_default(), base_reg + 1);
|
|
|
|
// type
|
|
program.emit_string8(column.ty_str.clone(), base_reg + 2);
|
|
|
|
// notnull
|
|
program.emit_bool(column.notnull, base_reg + 3);
|
|
|
|
// dflt_value
|
|
match &column.default {
|
|
None => {
|
|
program.emit_null(base_reg + 4, None);
|
|
}
|
|
Some(expr) => {
|
|
program.emit_string8(expr.to_string(), base_reg + 4);
|
|
}
|
|
}
|
|
|
|
// pk
|
|
program.emit_bool(column.primary_key, base_reg + 5);
|
|
|
|
program.emit_result_row(base_reg, 6);
|
|
}
|
|
}
|
|
let col_names = ["cid", "name", "type", "notnull", "dflt_value", "pk"];
|
|
for name in col_names {
|
|
program.add_pragma_result_column(name.into());
|
|
}
|
|
}
|
|
PragmaName::UserVersion => {
|
|
program.emit_insn(Insn::ReadCookie {
|
|
db: 0,
|
|
dest: register,
|
|
cookie: Cookie::UserVersion,
|
|
});
|
|
program.add_pragma_result_column(pragma.to_string());
|
|
program.emit_result_row(register, 1);
|
|
}
|
|
PragmaName::SchemaVersion => {
|
|
program.emit_insn(Insn::ReadCookie {
|
|
db: 0,
|
|
dest: register,
|
|
cookie: Cookie::SchemaVersion,
|
|
});
|
|
program.add_pragma_result_column(pragma.to_string());
|
|
program.emit_result_row(register, 1);
|
|
}
|
|
PragmaName::PageSize => {
|
|
program.emit_int(database_header.lock().get_page_size().into(), register);
|
|
program.emit_result_row(register, 1);
|
|
program.add_pragma_result_column(pragma.to_string());
|
|
}
|
|
PragmaName::AutoVacuum => {
|
|
let auto_vacuum_mode = pager.get_auto_vacuum_mode();
|
|
let auto_vacuum_mode_i64: i64 = match auto_vacuum_mode {
|
|
AutoVacuumMode::None => 0,
|
|
AutoVacuumMode::Full => 1,
|
|
AutoVacuumMode::Incremental => 2,
|
|
};
|
|
let register = program.alloc_register();
|
|
program.emit_insn(Insn::Int64 {
|
|
_p1: 0,
|
|
out_reg: register,
|
|
_p3: 0,
|
|
value: auto_vacuum_mode_i64,
|
|
});
|
|
program.emit_result_row(register, 1);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn update_auto_vacuum_mode(
|
|
auto_vacuum_mode: AutoVacuumMode,
|
|
largest_root_page_number: u32,
|
|
header: Arc<SpinLock<DatabaseHeader>>,
|
|
pager: Rc<Pager>,
|
|
) -> crate::Result<()> {
|
|
let mut header_guard = header.lock();
|
|
header_guard.vacuum_mode_largest_root_page = largest_root_page_number;
|
|
pager.set_auto_vacuum_mode(auto_vacuum_mode);
|
|
pager.write_database_header(&header_guard)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn update_cache_size(
|
|
value: i64,
|
|
header: Arc<SpinLock<DatabaseHeader>>,
|
|
pager: Rc<Pager>,
|
|
connection: Weak<crate::Connection>,
|
|
) -> crate::Result<()> {
|
|
let mut cache_size_unformatted: i64 = value;
|
|
let mut cache_size = if cache_size_unformatted < 0 {
|
|
let kb = cache_size_unformatted.abs() * 1024;
|
|
let page_size = header.lock().get_page_size();
|
|
kb / page_size as i64
|
|
} else {
|
|
value
|
|
} as usize;
|
|
|
|
if cache_size < MIN_PAGE_CACHE_SIZE {
|
|
cache_size = MIN_PAGE_CACHE_SIZE;
|
|
cache_size_unformatted = MIN_PAGE_CACHE_SIZE as i64;
|
|
}
|
|
connection
|
|
.upgrade()
|
|
.unwrap()
|
|
.set_cache_size(cache_size_unformatted as i32);
|
|
|
|
// update cache size
|
|
pager
|
|
.change_page_cache_size(cache_size)
|
|
.expect("couldn't update page cache size");
|
|
|
|
Ok(())
|
|
}
|