From a6d1a7cb56ca134271144865c1f87cb4c41c966e Mon Sep 17 00:00:00 2001 From: Raul Ferrando Date: Mon, 16 Dec 2024 18:01:10 +0100 Subject: [PATCH 1/2] pragma: inital approach to handle pragma statements This change refactors how PRAGMA statements are handled, introducing a more organized and extensible structure to simplify adding new PRAGMA properties in the future. Previously, only the `cache_size` PRAGMA was supported. With this update, support for the `journal_mode` PRAGMA has been added. --- core/translate/mod.rs | 144 +++++++++++------- testing/pragma.test | 4 + vendored/sqlite3-parser/src/parser/ast/mod.rs | 23 ++- 3 files changed, 118 insertions(+), 53 deletions(-) diff --git a/core/translate/mod.rs b/core/translate/mod.rs index ef06e1467..593886cf9 100644 --- a/core/translate/mod.rs +++ b/core/translate/mod.rs @@ -18,6 +18,7 @@ pub(crate) mod select; use std::cell::RefCell; use std::fmt::Display; use std::rc::{Rc, Weak}; +use std::str::FromStr; use crate::schema::Schema; use crate::storage::pager::Pager; @@ -26,8 +27,8 @@ use crate::vdbe::{builder::ProgramBuilder, Insn, Program}; use crate::{bail_parse_error, Connection, Result}; use insert::translate_insert; use select::translate_select; -use sqlite3_parser::ast; use sqlite3_parser::ast::fmt::ToTokens; +use sqlite3_parser::ast::{self, PragmaName}; /// Translate SQL statement into bytecode program. pub fn translate( @@ -305,38 +306,17 @@ fn translate_pragma( let mut write = false; 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, - }); + let pragma_name = &name.name.0; + query_pragma(pragma_name, database_header.clone(), &mut program); } Some(ast::PragmaBody::Equals(value)) => { - let value_to_update = match value { - ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { - numeric_value.parse::().unwrap() - } - ast::Expr::Unary(ast::UnaryOperator::Negative, expr) => match *expr { - ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { - -numeric_value.parse::().unwrap() - } - _ => 0, - }, - _ => 0, - }; write = true; update_pragma( &name.name.0, - value_to_update, + value, database_header.clone(), pager, + &mut program, ); } Some(ast::PragmaBody::Call(_)) => { @@ -357,36 +337,96 @@ fn translate_pragma( Ok(program.build(database_header, connection)) } -fn update_pragma(name: &str, value: i64, header: Rc>, pager: Rc) { - 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)); +fn update_pragma( + name: &str, + value: ast::Expr, + header: Rc>, + pager: Rc, + program: &mut ProgramBuilder, +) { + let pragma = PragmaName::from_str(name).expect("provided pragma not valid"); + match pragma { + PragmaName::CacheSize => { + let cache_size = match value { + ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { + numeric_value.parse::().unwrap() + } + ast::Expr::Unary(ast::UnaryOperator::Negative, expr) => match *expr { + ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { + -numeric_value.parse::().unwrap() + } + _ => 0, + }, + _ => 0, + }; - // update in disk - let header_copy = header.borrow().clone(); - pager.write_database_header(&header_copy); + update_cache_size(cache_size, header, pager); + } + PragmaName::JournalMode => { + query_pragma("journal_mode", header, program); + } + _ => todo!("pragma `{name}`"), + } +} - // update cache size - pager.change_page_cache_size(cache_size); +fn query_pragma( + name: &str, + database_header: Rc>, + program: &mut ProgramBuilder, +) { + let pragma = PragmaName::from_str(name).expect("provided pragma not valid"); + let register = program.alloc_register(); + match pragma { + PragmaName::CacheSize => { + program.emit_insn(Insn::Integer { + value: database_header.borrow().default_cache_size.into(), + dest: register, + }); } - _ => todo!(), + PragmaName::JournalMode => { + program.emit_insn(Insn::String8 { + value: "wal".into(), + dest: register, + }); + } + _ => { + todo!("pragma `{name}`"); + } + } + + let next_register = program.next_free_register(); + program.emit_insn(Insn::ResultRow { + start_reg: register, + count: next_register - register, + }); +} + +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_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> { diff --git a/testing/pragma.test b/testing/pragma.test index 74b7ad339..ce6b2996c 100755 --- a/testing/pragma.test +++ b/testing/pragma.test @@ -6,3 +6,7 @@ source $testdir/tester.tcl do_execsql_test pragma-cache-size { PRAGMA cache_size } {-2000} + +do_execsql_test pragma-update-journal-mode-wal { + PRAGMA journal_mode=WAL +} {wal} diff --git a/vendored/sqlite3-parser/src/parser/ast/mod.rs b/vendored/sqlite3-parser/src/parser/ast/mod.rs index 4ff8746f3..7ee4d58f3 100644 --- a/vendored/sqlite3-parser/src/parser/ast/mod.rs +++ b/vendored/sqlite3-parser/src/parser/ast/mod.rs @@ -1572,11 +1572,32 @@ pub enum PragmaBody { /// function call Call(PragmaValue), } - /// `PRAGMA` value // https://sqlite.org/syntax/pragma-value.html pub type PragmaValue = Expr; // TODO +/// `PRAGMA` value +// https://sqlite.org/pragma.html +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PragmaName { + /// `cache_size` pragma + CacheSize, + /// `journal_mode` pragma + JournalMode, +} + +impl FromStr for PragmaName { + type Err = (); + + fn from_str(input: &str) -> Result { + match input { + "cache_size" => Ok(PragmaName::CacheSize), + "journal_mode" => Ok(PragmaName::JournalMode), + _ => Err(()), + } + } +} + /// `CREATE TRIGGER` time #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum TriggerTime { From d74012bb59542894a06fc8396013fc4d10e7d3b4 Mon Sep 17 00:00:00 2001 From: Raul Ferrando Date: Wed, 18 Dec 2024 17:10:13 +0100 Subject: [PATCH 2/2] fix pragma parsing potential errors --- core/translate/mod.rs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/core/translate/mod.rs b/core/translate/mod.rs index 593886cf9..2e5d86141 100644 --- a/core/translate/mod.rs +++ b/core/translate/mod.rs @@ -307,7 +307,7 @@ fn translate_pragma( match body { None => { let pragma_name = &name.name.0; - query_pragma(pragma_name, database_header.clone(), &mut program); + query_pragma(pragma_name, database_header.clone(), &mut program)?; } Some(ast::PragmaBody::Equals(value)) => { write = true; @@ -317,7 +317,7 @@ fn translate_pragma( database_header.clone(), pager, &mut program, - ); + )?; } Some(ast::PragmaBody::Call(_)) => { todo!() @@ -343,8 +343,11 @@ fn update_pragma( header: Rc>, pager: Rc, program: &mut ProgramBuilder, -) { - let pragma = PragmaName::from_str(name).expect("provided pragma not valid"); +) -> Result<()> { + let pragma = match PragmaName::from_str(name) { + Ok(pragma) => pragma, + Err(()) => bail_parse_error!("Not a valid pragma name"), + }; match pragma { PragmaName::CacheSize => { let cache_size = match value { @@ -355,15 +358,16 @@ fn update_pragma( ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { -numeric_value.parse::().unwrap() } - _ => 0, + _ => bail_parse_error!("Not a valid value"), }, - _ => 0, + _ => bail_parse_error!("Not a valid value"), }; - update_cache_size(cache_size, header, pager); + Ok(()) } PragmaName::JournalMode => { - query_pragma("journal_mode", header, program); + query_pragma("journal_mode", header, program)?; + Ok(()) } _ => todo!("pragma `{name}`"), } @@ -373,8 +377,11 @@ fn query_pragma( name: &str, database_header: Rc>, program: &mut ProgramBuilder, -) { - let pragma = PragmaName::from_str(name).expect("provided pragma not valid"); +) -> Result<()> { + let pragma = match PragmaName::from_str(name) { + Ok(pragma) => pragma, + Err(()) => bail_parse_error!("Not a valid pragma name"), + }; let register = program.alloc_register(); match pragma { PragmaName::CacheSize => { @@ -394,11 +401,11 @@ fn query_pragma( } } - let next_register = program.next_free_register(); program.emit_insn(Insn::ResultRow { start_reg: register, - count: next_register - register, + count: 1, }); + Ok(()) } fn update_cache_size(value: i64, header: Rc>, pager: Rc) {