From 2d4bf2eb629792eb030d961a10d7167cc103f67e Mon Sep 17 00:00:00 2001 From: sonhmai <> Date: Mon, 3 Feb 2025 09:20:37 +0700 Subject: [PATCH] core: move pragma statement bytecode generator to its own file. --- core/translate/mod.rs | 264 +------------------------------------- core/translate/pragma.rs | 269 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 259 deletions(-) create mode 100644 core/translate/pragma.rs diff --git a/core/translate/mod.rs b/core/translate/mod.rs index 26bcf49de..8a046b9b2 100644 --- a/core/translate/mod.rs +++ b/core/translate/mod.rs @@ -18,28 +18,25 @@ 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 select; pub(crate) mod subquery; use crate::schema::Schema; use crate::storage::pager::Pager; -use crate::storage::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE}; -use crate::storage::wal::CheckpointMode; +use crate::storage::sqlite3_ondisk::DatabaseHeader; use crate::translate::delete::translate_delete; -use crate::util::{normalize_ident, PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX}; +use crate::util::PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX; use crate::vdbe::builder::CursorType; -use crate::vdbe::BranchOffset; use crate::vdbe::{builder::ProgramBuilder, insn::Insn, Program}; use crate::{bail_parse_error, Connection, LimboError, Result, SymbolTable}; use insert::translate_insert; use select::translate_select; -use sqlite3_parser::ast::{self, fmt::ToTokens, PragmaName}; +use sqlite3_parser::ast::{self, fmt::ToTokens}; use std::cell::RefCell; use std::fmt::Display; use std::rc::{Rc, Weak}; -use std::str::FromStr; -use strum::IntoEnumIterator; /// Translate SQL statement into bytecode program. pub fn translate( @@ -92,7 +89,7 @@ pub fn translate( 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( + pragma::translate_pragma( &mut program, &schema, &name, @@ -519,257 +516,6 @@ enum PrimaryKeyDefinitionType<'a> { Composite, } -fn list_pragmas( - program: &mut ProgramBuilder, - init_label: BranchOffset, - start_offset: BranchOffset, -) { - for x in PragmaName::iter() { - let register = program.emit_string8_new_reg(x.to_string()); - program.emit_result_row(register, 1); - } - - program.emit_halt(); - program.resolve_label(init_label, program.offset()); - program.emit_constant_insns(); - program.emit_goto(start_offset); -} - -fn translate_pragma( - program: &mut ProgramBuilder, - schema: &Schema, - name: &ast::QualifiedName, - body: Option, - database_header: Rc>, - pager: Rc, -) -> Result<()> { - let init_label = program.emit_init(); - let start_offset = program.offset(); - let mut write = false; - - if name.name.0.to_lowercase() == "pragma_list" { - list_pragmas(program, init_label, start_offset); - return Ok(()); - } - - 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(), program)?; - } - Some(ast::PragmaBody::Equals(value)) => match pragma { - PragmaName::TableInfo => { - query_pragma( - pragma, - schema, - Some(value), - database_header.clone(), - program, - )?; - } - _ => { - write = true; - update_pragma( - pragma, - schema, - value, - database_header.clone(), - pager, - program, - )?; - } - }, - Some(ast::PragmaBody::Call(value)) => match pragma { - PragmaName::TableInfo => { - query_pragma( - pragma, - schema, - Some(value), - database_header.clone(), - program, - )?; - } - _ => { - todo!() - } - }, - }; - program.emit_halt(); - program.resolve_label(init_label, program.offset()); - program.emit_transaction(write); - program.emit_constant_insns(); - program.emit_goto(start_offset); - - Ok(()) -} - -fn update_pragma( - pragma: PragmaName, - schema: &Schema, - value: ast::Expr, - header: Rc>, - pager: Rc, - program: &mut ProgramBuilder, -) -> Result<()> { - match pragma { - PragmaName::CacheSize => { - let cache_size = match value { - ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { - numeric_value.parse::()? - } - ast::Expr::Unary(ast::UnaryOperator::Negative, expr) => match *expr { - ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { - -numeric_value.parse::()? - } - _ => bail_parse_error!("Not a valid value"), - }, - _ => bail_parse_error!("Not a valid value"), - }; - update_cache_size(cache_size, header, pager); - Ok(()) - } - PragmaName::JournalMode => { - query_pragma(PragmaName::JournalMode, schema, None, header, program)?; - Ok(()) - } - PragmaName::WalCheckpoint => { - query_pragma(PragmaName::WalCheckpoint, schema, None, header, program)?; - Ok(()) - } - PragmaName::PageCount => { - query_pragma(PragmaName::PageCount, schema, None, header, program)?; - Ok(()) - } - 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!(); - } - } -} - -fn query_pragma( - pragma: PragmaName, - schema: &Schema, - value: Option, - database_header: Rc>, - program: &mut ProgramBuilder, -) -> Result<()> { - let register = program.alloc_register(); - match pragma { - PragmaName::CacheSize => { - program.emit_int( - database_header.borrow().default_page_cache_size.into(), - register, - ); - program.emit_result_row(register, 1); - } - PragmaName::JournalMode => { - program.emit_string8("wal".into(), register); - program.emit_result_row(register, 1); - } - 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); - } - 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(), 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); - } - 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); - } - } - } - } - - Ok(()) -} - -fn update_cache_size(value: i64, header: Rc>, pager: Rc) { - let mut cache_size_unformatted: i64 = 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_page_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); -} - struct TableFormatter<'a> { body: &'a ast::CreateTableBody, } diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs new file mode 100644 index 000000000..df0041331 --- /dev/null +++ b/core/translate/pragma.rs @@ -0,0 +1,269 @@ +//! VDBE bytecode generation for pragma statements. +//! More info: https://www.sqlite.org/pragma.html. + +use sqlite3_parser::ast; +use sqlite3_parser::ast::PragmaName; +use std::cell::RefCell; +use std::rc::Rc; + +use crate::schema::Schema; +use crate::storage::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE}; +use crate::storage::wal::CheckpointMode; +use crate::util::normalize_ident; +use crate::vdbe::builder::ProgramBuilder; +use crate::vdbe::insn::Insn; +use crate::vdbe::BranchOffset; +use crate::{bail_parse_error, Pager}; +use std::str::FromStr; +use strum::IntoEnumIterator; + +fn list_pragmas( + program: &mut ProgramBuilder, + init_label: BranchOffset, + start_offset: BranchOffset, +) { + for x in PragmaName::iter() { + let register = program.emit_string8_new_reg(x.to_string()); + program.emit_result_row(register, 1); + } + + program.emit_halt(); + program.resolve_label(init_label, program.offset()); + program.emit_constant_insns(); + program.emit_goto(start_offset); +} + +pub fn translate_pragma( + program: &mut ProgramBuilder, + schema: &Schema, + name: &ast::QualifiedName, + body: Option, + database_header: Rc>, + pager: Rc, +) -> crate::Result<()> { + let init_label = program.emit_init(); + let start_offset = program.offset(); + let mut write = false; + + if name.name.0.to_lowercase() == "pragma_list" { + list_pragmas(program, init_label, start_offset); + return Ok(()); + } + + 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(), program)?; + } + Some(ast::PragmaBody::Equals(value)) => match pragma { + PragmaName::TableInfo => { + query_pragma( + pragma, + schema, + Some(value), + database_header.clone(), + program, + )?; + } + _ => { + write = true; + update_pragma( + pragma, + schema, + value, + database_header.clone(), + pager, + program, + )?; + } + }, + Some(ast::PragmaBody::Call(value)) => match pragma { + PragmaName::TableInfo => { + query_pragma( + pragma, + schema, + Some(value), + database_header.clone(), + program, + )?; + } + _ => { + todo!() + } + }, + }; + program.emit_halt(); + program.resolve_label(init_label, program.offset()); + program.emit_transaction(write); + program.emit_constant_insns(); + program.emit_goto(start_offset); + + Ok(()) +} + +fn update_pragma( + pragma: PragmaName, + schema: &Schema, + value: ast::Expr, + header: Rc>, + pager: Rc, + program: &mut ProgramBuilder, +) -> crate::Result<()> { + match pragma { + PragmaName::CacheSize => { + let cache_size = match value { + ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { + numeric_value.parse::()? + } + ast::Expr::Unary(ast::UnaryOperator::Negative, expr) => match *expr { + ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { + -numeric_value.parse::()? + } + _ => bail_parse_error!("Not a valid value"), + }, + _ => bail_parse_error!("Not a valid value"), + }; + update_cache_size(cache_size, header, pager); + Ok(()) + } + PragmaName::JournalMode => { + query_pragma(PragmaName::JournalMode, schema, None, header, program)?; + Ok(()) + } + PragmaName::WalCheckpoint => { + query_pragma(PragmaName::WalCheckpoint, schema, None, header, program)?; + Ok(()) + } + PragmaName::PageCount => { + query_pragma(PragmaName::PageCount, schema, None, header, program)?; + Ok(()) + } + 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!(); + } + } +} + +fn query_pragma( + pragma: PragmaName, + schema: &Schema, + value: Option, + database_header: Rc>, + program: &mut ProgramBuilder, +) -> crate::Result<()> { + let register = program.alloc_register(); + match pragma { + PragmaName::CacheSize => { + program.emit_int( + database_header.borrow().default_page_cache_size.into(), + register, + ); + program.emit_result_row(register, 1); + } + PragmaName::JournalMode => { + program.emit_string8("wal".into(), register); + program.emit_result_row(register, 1); + } + 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); + } + 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(), 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); + } + 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); + } + } + } + } + + Ok(()) +} + +fn update_cache_size(value: i64, header: Rc>, pager: Rc) { + let mut cache_size_unformatted: i64 = 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_page_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); +}