diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index 5c66445f9..7859d64ed 100644 --- a/core/translate/pragma.rs +++ b/core/translate/pragma.rs @@ -10,10 +10,10 @@ use crate::fast_lock::SpinLock; 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::util::{normalize_ident, parse_signed_number}; use crate::vdbe::builder::{ProgramBuilder, ProgramBuilderOpts, QueryMode}; use crate::vdbe::insn::{Cookie, Insn}; -use crate::{bail_parse_error, Pager}; +use crate::{bail_parse_error, Pager, Value}; use std::str::FromStr; use strum::IntoEnumIterator; @@ -142,27 +142,19 @@ fn update_pragma( Ok(()) } PragmaName::UserVersion => { - let version_value = 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"), + let data = parse_signed_number(&value)?; + let version_value = match data { + Value::Integer(i) => i as i32, + Value::Float(f) => f as i32, + _ => unreachable!(), }; - let mut header_guard = header.lock(); - - // update in-memory - header_guard.user_version = version_value; - - // update in disk - pager.write_database_header(&header_guard); - + program.emit_insn(Insn::SetCookie { + db: 0, + cookie: Cookie::UserVersion, + value: version_value, + p5: 1, + }); Ok(()) } PragmaName::SchemaVersion => { diff --git a/core/util.rs b/core/util.rs index 909145bd0..f6b83ebfd 100644 --- a/core/util.rs +++ b/core/util.rs @@ -4,7 +4,9 @@ use crate::{ types::{Value, ValueType}, LimboError, OpenFlags, Result, Statement, StepResult, SymbolTable, IO, }; -use limbo_sqlite3_parser::ast::{self, CreateTableBody, Expr, FunctionTail, Literal}; +use limbo_sqlite3_parser::ast::{ + self, CreateTableBody, Expr, FunctionTail, Literal, UnaryOperator, +}; use std::{rc::Rc, sync::Arc}; pub trait RoundToPrecision { @@ -1011,6 +1013,27 @@ pub fn parse_numeric_literal(text: &str) -> Result { Ok(Value::Float(float_value)) } +pub fn parse_signed_number(expr: &Expr) -> Result { + match expr { + Expr::Literal(Literal::Numeric(num)) => parse_numeric_literal(num), + Expr::Unary(op, expr) => match (op, expr.as_ref()) { + (UnaryOperator::Negative, Expr::Literal(Literal::Numeric(num))) => { + let data = "-".to_owned() + &num.to_string(); + parse_numeric_literal(&data) + } + (UnaryOperator::Positive, Expr::Literal(Literal::Numeric(num))) => { + parse_numeric_literal(num) + } + _ => Err(LimboError::InvalidArgument( + "signed-number must follow the format: ([+|-] numeric-literal)".to_string(), + )), + }, + _ => Err(LimboError::InvalidArgument( + "signed-number must follow the format: ([+|-] numeric-literal)".to_string(), + )), + } +} + // for TVF's we need these at planning time so we cannot emit translate_expr pub fn vtable_args(args: &[ast::Expr]) -> Vec { let mut vtable_args = Vec::new(); diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index dddfd2098..2071434bc 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -4520,6 +4520,37 @@ pub fn op_read_cookie( Ok(InsnFunctionStepResult::Step) } +pub fn op_set_cookie( + program: &Program, + state: &mut ProgramState, + insn: &Insn, + pager: &Rc, + mv_store: Option<&Rc>, +) -> Result { + let Insn::SetCookie { + db, + cookie, + value, + p5, + } = insn + else { + unreachable!("unexpected Insn {:?}", insn) + }; + if *db > 0 { + todo!("temp databases not implemented yet"); + } + match cookie { + Cookie::UserVersion => { + let mut header_guard = pager.db_header.lock(); + header_guard.user_version = *value; + pager.write_database_header(&*header_guard); + } + cookie => todo!("{cookie:?} is not yet implement for SetCookie"), + } + state.pc += 1; + Ok(InsnFunctionStepResult::Step) +} + pub fn op_shift_right( program: &Program, state: &mut ProgramState, diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 4055fb072..425efee2d 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1373,6 +1373,20 @@ pub fn insn_to_str( 0, "".to_string(), ), + Insn::SetCookie { + db, + cookie, + value, + p5, + } => ( + "SetCookie", + *db as i32, + *cookie as i32, + *value, + Value::build_text(""), + *p5, + "".to_string(), + ), Insn::AutoCommit { auto_commit, rollback, diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index f5ff8352d..b6c70efe4 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -844,6 +844,14 @@ pub enum Insn { dest: usize, cookie: Cookie, }, + /// Write the value in register P3 into cookie number P2 of database P1. + /// If P2 is the SCHEMA_VERSION cookie (cookie number 1) then the internal schema version is set to P3-P5 + SetCookie { + db: usize, + cookie: Cookie, + value: i32, + p5: u16, + }, /// Open a new cursor P1 to a transient table. OpenEphemeral { cursor_id: usize, @@ -1010,6 +1018,7 @@ impl Insn { Insn::Noop => execute::op_noop, Insn::PageCount { .. } => execute::op_page_count, Insn::ReadCookie { .. } => execute::op_read_cookie, + Insn::SetCookie { .. } => execute::op_set_cookie, Insn::OpenEphemeral { .. } | Insn::OpenAutoindex { .. } => execute::op_open_ephemeral, Insn::Once { .. } => execute::op_once, Insn::Found { .. } | Insn::NotFound { .. } => execute::op_found, diff --git a/testing/pragma.test b/testing/pragma.test index fd98b19b9..bdc3b1102 100755 --- a/testing/pragma.test +++ b/testing/pragma.test @@ -55,3 +55,13 @@ do_execsql_test_on_specific_db ":memory:" pragma-user-version-update { PRAGMA user_version = 42; PRAGMA user_version; } {42} + +do_execsql_test_on_specific_db ":memory:" pragma-user-version-negative-value { + PRAGMA user_version = -10; + PRAGMA user_version; +} {-10} + +do_execsql_test_on_specific_db ":memory:" pragma-user-version-float-value { + PRAGMA user_version = 10.9; + PRAGMA user_version; +} {10}