From 98e9d334787199c2e912662eefcab9388f6a49e0 Mon Sep 17 00:00:00 2001 From: Jonathan Webb Date: Thu, 6 Feb 2025 11:10:27 -0500 Subject: [PATCH] Add read implementation of user_version pragma with ReadCookie opcode --- COMPAT.md | 4 +-- core/storage/sqlite3_ondisk.rs | 4 +-- core/translate/pragma.rs | 15 +++++++++++- core/vdbe/explain.rs | 9 +++++++ core/vdbe/insn.rs | 23 ++++++++++++++++++ core/vdbe/mod.rs | 14 ++++++++++- testing/pragma.test | 8 ++++++ testing/testing_user_version_10.db | Bin 0 -> 4096 bytes vendored/sqlite3-parser/src/parser/ast/mod.rs | 2 ++ 9 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 testing/testing_user_version_10.db diff --git a/COMPAT.md b/COMPAT.md index 1c2b9b227..045d4205f 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -160,7 +160,7 @@ The current status of Limbo is: | PRAGMA temp_store_directory | Not Needed | deprecated in SQLite | | PRAGMA threads | No | | | PRAGMA trusted_schema | No | | -| PRAGMA user_version | No | | +| PRAGMA user_version | Partial | Only read implemented | | PRAGMA vdbe_addoptrace | No | | | PRAGMA vdbe_debug | No | | | PRAGMA vdbe_listing | No | | @@ -514,7 +514,7 @@ Modifiers: | PrevAsync | Yes | | | PrevAwait | Yes | | | Program | No | | -| ReadCookie | No | | +| ReadCookie | Partial| no temp databases, only user_version supported | | Real | Yes | | | RealAffinity | Yes | | | Remainder | Yes | | diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 56bc959f9..c0b889f91 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -128,7 +128,7 @@ pub struct DatabaseHeader { text_encoding: u32, /// The "user version" as read and set by the user_version pragma. - user_version: u32, + pub user_version: u32, /// True (non-zero) for incremental-vacuum mode. False (zero) otherwise. incremental_vacuum_enabled: u32, @@ -232,7 +232,7 @@ impl Default for DatabaseHeader { default_page_cache_size: 500, // pages vacuum_mode_largest_root_page: 0, text_encoding: 1, // utf-8 - user_version: 1, + user_version: 0, incremental_vacuum_enabled: 0, application_id: 0, reserved_for_expansion: [0; 20], diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index ebc854442..e31e732ce 100644 --- a/core/translate/pragma.rs +++ b/core/translate/pragma.rs @@ -11,7 +11,7 @@ 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, ProgramBuilderOpts, QueryMode}; -use crate::vdbe::insn::Insn; +use crate::vdbe::insn::{Cookie, Insn}; use crate::vdbe::BranchOffset; use crate::{bail_parse_error, Pager}; use std::str::FromStr; @@ -148,6 +148,10 @@ fn update_pragma( query_pragma(PragmaName::PageCount, schema, None, header, program)?; Ok(()) } + PragmaName::UserVersion => { + // TODO: Implement updating user_version + todo!("updating user_version not yet implemented") + } 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 @@ -241,6 +245,15 @@ fn query_pragma( } } } + PragmaName::UserVersion => { + program.emit_transaction(false); + program.emit_insn(Insn::ReadCookie { + db: 0, + dest: register, + cookie: Cookie::UserVersion, + }); + program.emit_result_row(register, 1); + } } Ok(()) diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index a0bb63023..8ec75a187 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1232,6 +1232,15 @@ pub fn insn_to_str( 0, "".to_string(), ), + Insn::ReadCookie { db, dest, cookie } => ( + "ReadCookie", + *db as i32, + *dest as i32, + *cookie as i32, + OwnedValue::build_text(Rc::new("".to_string())), + 0, + "".to_string(), + ), }; format!( "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 223f321aa..0766b02d4 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -639,6 +639,29 @@ pub enum Insn { db: usize, dest: usize, }, + /// Read cookie number P3 from database P1 and write it into register P2 + ReadCookie { + db: usize, + dest: usize, + cookie: Cookie, + }, +} + +// TODO: Add remaining cookies. +#[derive(Description, Debug, Clone, Copy)] +pub enum Cookie { + /// The schema cookie. + SchemaVersion = 1, + /// The schema format number. Supported schema formats are 1, 2, 3, and 4. + DatabaseFormat = 2, + /// Default page cache size. + DefaultPageCacheSize = 3, + /// The page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise. + LargestRootPageNumber = 4, + /// The database text encoding. A value of 1 means UTF-8. A value of 2 means UTF-16le. A value of 3 means UTF-16be. + DatabaseTextEncoding = 5, + /// The "user version" as read and set by the user_version pragma. + UserVersion = 6, } fn cast_text_to_numerical(value: &str) -> OwnedValue { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 5003b72c6..81427fb00 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -55,7 +55,7 @@ use crate::{resolve_ext_path, Connection, Result, TransactionState, DATABASE_VER use insn::{ exec_add, exec_and, exec_bit_and, exec_bit_not, exec_bit_or, exec_boolean_not, exec_concat, exec_divide, exec_multiply, exec_or, exec_remainder, exec_shift_left, exec_shift_right, - exec_subtract, + exec_subtract, Cookie, }; use likeop::{construct_like_escape_arg, exec_glob, exec_like_with_escape}; use rand::distributions::{Distribution, Uniform}; @@ -2662,6 +2662,18 @@ impl Program { parse_schema_rows(Some(stmt), &mut schema, conn.pager.io.clone())?; state.pc += 1; } + Insn::ReadCookie { db, dest, cookie } => { + if *db > 0 { + // TODO: implement temp databases + todo!("temp databases not implemented yet"); + } + let cookie_value = match cookie { + Cookie::UserVersion => pager.db_header.borrow().user_version.into(), + cookie => todo!("{cookie:?} is not yet implement for ReadCookie"), + }; + state.registers[*dest] = OwnedValue::Integer(cookie_value); + state.pc += 1; + } Insn::ShiftRight { lhs, rhs, dest } => { state.registers[*dest] = exec_shift_right(&state.registers[*lhs], &state.registers[*rhs]); diff --git a/testing/pragma.test b/testing/pragma.test index 436ad48e1..c478c032c 100755 --- a/testing/pragma.test +++ b/testing/pragma.test @@ -41,3 +41,11 @@ do_execsql_test_on_specific_db ":memory:" pragma-page-count-table { CREATE TABLE foo(bar); PRAGMA page_count } {2} + +do_execsql_test_on_specific_db "testing/testing_user_version_10.db" pragma-user-version-user-set { + PRAGMA user_version +} {10} + +do_execsql_test_on_specific_db ":memory:" pragma-user-version-default { + PRAGMA user_version +} {0} \ No newline at end of file diff --git a/testing/testing_user_version_10.db b/testing/testing_user_version_10.db new file mode 100644 index 0000000000000000000000000000000000000000..d51d013f1a4fba55c4eb1060af8cd0adcd3229a7 GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}R))P*7lCU|@t|AcYff;go?$Ff!=XMe%~z0t8f! rN{@!XXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD&@2Q1bP5LX literal 0 HcmV?d00001 diff --git a/vendored/sqlite3-parser/src/parser/ast/mod.rs b/vendored/sqlite3-parser/src/parser/ast/mod.rs index f149322b3..e42897029 100644 --- a/vendored/sqlite3-parser/src/parser/ast/mod.rs +++ b/vendored/sqlite3-parser/src/parser/ast/mod.rs @@ -1598,6 +1598,8 @@ pub enum PragmaName { PageCount, /// returns information about the columns of a table TableInfo, + /// Returns the user version of the database file. + UserVersion, /// trigger a checkpoint to run on database(s) if WAL is enabled WalCheckpoint, }