From d9d3a5ecbb2d5e8ae27b7f8a638b81c12897b9c2 Mon Sep 17 00:00:00 2001 From: meteorgan Date: Mon, 26 May 2025 23:28:15 +0800 Subject: [PATCH 1/3] Use the SetCookie opcode to implement user_version pragma --- core/translate/pragma.rs | 30 +++++++++++++++++------------- core/vdbe/execute.rs | 31 +++++++++++++++++++++++++++++++ core/vdbe/explain.rs | 14 ++++++++++++++ core/vdbe/insn.rs | 9 +++++++++ 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index 5c66445f9..5a4716672 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_numeric_literal}; 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,31 @@ fn update_pragma( Ok(()) } PragmaName::UserVersion => { - let version_value = match value { + let data = match value { ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { - numeric_value.parse::()? + parse_numeric_literal(&numeric_value)? } ast::Expr::Unary(ast::UnaryOperator::Negative, expr) => match *expr { ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { - -numeric_value.parse::()? + let data = "-".to_owned() + numeric_value.as_str(); + parse_numeric_literal(&data)? } _ => bail_parse_error!("Not a valid value"), }, _ => bail_parse_error!("Not a valid 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/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, From 2f82762ca2d5ebcc28c1d38466a9510a0fdcc0eb Mon Sep 17 00:00:00 2001 From: meteorgan Date: Wed, 28 May 2025 00:33:08 +0800 Subject: [PATCH 2/3] add function parse_signed_number --- core/translate/pragma.rs | 16 ++-------------- core/util.rs | 25 ++++++++++++++++++++++++- database | Bin 0 -> 8192 bytes 3 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 database diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index 5a4716672..7859d64ed 100644 --- a/core/translate/pragma.rs +++ b/core/translate/pragma.rs @@ -10,7 +10,7 @@ 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, parse_numeric_literal}; +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, Value}; @@ -142,19 +142,7 @@ fn update_pragma( Ok(()) } PragmaName::UserVersion => { - let data = match value { - ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { - parse_numeric_literal(&numeric_value)? - } - ast::Expr::Unary(ast::UnaryOperator::Negative, expr) => match *expr { - ast::Expr::Literal(ast::Literal::Numeric(numeric_value)) => { - let data = "-".to_owned() + numeric_value.as_str(); - parse_numeric_literal(&data)? - } - _ => 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, 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/database b/database new file mode 100644 index 0000000000000000000000000000000000000000..564a67b357b84fccabebbe5d215c2ae5a4d8323c GIT binary patch literal 8192 zcmeI#u?_)25XSM@jp&3etPBnO8CBu09{jsAK7g_cB!#5ohJCKGU-C?KODpzh`vWh*igax$af$5d;uG009IL zKmY**5I_I{1Q7T|VB*^SByo#aZV#aeb@T3izRJ=f(;}VanLalh|Y9Y8J{ZU6uP literal 0 HcmV?d00001 From 86249d9c288736113025378980a4ebbcff862df5 Mon Sep 17 00:00:00 2001 From: meteorgan Date: Wed, 28 May 2025 00:47:09 +0800 Subject: [PATCH 3/3] add more tests for pragma user_version --- database | Bin 8192 -> 0 bytes testing/pragma.test | 10 ++++++++++ 2 files changed, 10 insertions(+) delete mode 100644 database diff --git a/database b/database deleted file mode 100644 index 564a67b357b84fccabebbe5d215c2ae5a4d8323c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8192 zcmeI#u?_)25XSM@jp&3etPBnO8CBu09{jsAK7g_cB!#5ohJCKGU-C?KODpzh`vWh*igax$af$5d;uG009IL zKmY**5I_I{1Q7T|VB*^SByo#aZV#aeb@T3izRJ=f(;}VanLalh|Y9Y8J{ZU6uP 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}