Skip to content

Commit

Permalink
Merge 'pragma: inital approach to handle pragma statements' from Raul…
Browse files Browse the repository at this point in the history
… Ferrando

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.

Reviewed-by: Pere Diaz Bou <[email protected]>

Closes #501
  • Loading branch information
pereman2 committed Dec 18, 2024
2 parents 26b4608 + d74012b commit 94d69c5
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 54 deletions.
153 changes: 100 additions & 53 deletions core/translate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand Down Expand Up @@ -305,39 +306,18 @@ 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::<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,
};
write = true;
update_pragma(
&name.name.0,
value_to_update,
value,
database_header.clone(),
pager,
);
&mut program,
)?;
}
Some(ast::PragmaBody::Call(_)) => {
todo!()
Expand All @@ -357,36 +337,103 @@ fn translate_pragma(
Ok(program.build(database_header, connection))
}

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;
}
fn update_pragma(
name: &str,
value: ast::Expr,
header: Rc<RefCell<DatabaseHeader>>,
pager: Rc<Pager>,
program: &mut ProgramBuilder,
) -> 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 {
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()
}
_ => 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("journal_mode", header, program)?;
Ok(())
}
_ => todo!("pragma `{name}`"),
}
}

fn query_pragma(
name: &str,
database_header: Rc<RefCell<DatabaseHeader>>,
program: &mut ProgramBuilder,
) -> 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 => {
program.emit_insn(Insn::Integer {
value: database_header.borrow().default_cache_size.into(),
dest: register,
});
}
PragmaName::JournalMode => {
program.emit_insn(Insn::String8 {
value: "wal".into(),
dest: register,
});
}
_ => {
todo!("pragma `{name}`");
}
}

// 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));
program.emit_insn(Insn::ResultRow {
start_reg: register,
count: 1,
});
Ok(())
}

// update in disk
let header_copy = header.borrow().clone();
pager.write_database_header(&header_copy);
fn update_cache_size(value: i64, header: Rc<RefCell<DatabaseHeader>>, pager: Rc<Pager>) {
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;

// update cache size
pager.change_page_cache_size(cache_size);
}
_ => todo!(),
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> {
Expand Down
4 changes: 4 additions & 0 deletions testing/pragma.test
Original file line number Diff line number Diff line change
Expand Up @@ -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}
23 changes: 22 additions & 1 deletion vendored/sqlite3-parser/src/parser/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self, Self::Err> {
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 {
Expand Down

0 comments on commit 94d69c5

Please sign in to comment.