From 6b40acabbcefcb51f7fc33c4ae6aaf453b7b0c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=A0=EC=9A=B0?= Date: Mon, 16 Sep 2024 18:38:42 +0900 Subject: [PATCH] Add support for sqlite_version() scalar function --- COMPAT.md | 2 +- core/function.rs | 3 +++ core/lib.rs | 8 +++++++- core/storage/sqlite3_ondisk.rs | 2 +- core/translate/expr.rs | 20 ++++++++++++++++++++ core/vdbe/mod.rs | 27 ++++++++++++++++++++++++--- testing/scalar-functions.test | 4 ++++ 7 files changed, 60 insertions(+), 6 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index 4b306c2c8..ae33ae452 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -103,7 +103,7 @@ This document describes the SQLite compatibility status of Limbo: | sqlite_compileoption_used(X) | No | | | sqlite_offset(X) | No | | | sqlite_source_id() | No | | -| sqlite_version() | No | | +| sqlite_version() | Yes | | | substr(X,Y,Z) | Yes | | | substr(X,Y) | Yes | | | substring(X,Y,Z) | Yes | | diff --git a/core/function.rs b/core/function.rs index 2cbd6166c..fd9c49248 100644 --- a/core/function.rs +++ b/core/function.rs @@ -65,6 +65,7 @@ pub enum ScalarFunc { Time, Unicode, Quote, + SqliteVersion, UnixEpoch, } @@ -96,6 +97,7 @@ impl ToString for ScalarFunc { ScalarFunc::Time => "time".to_string(), ScalarFunc::Unicode => "unicode".to_string(), ScalarFunc::Quote => "quote".to_string(), + ScalarFunc::SqliteVersion => "sqlite_version".to_string(), ScalarFunc::UnixEpoch => "unixepoch".to_string(), } } @@ -160,6 +162,7 @@ impl Func { "time" => Ok(Func::Scalar(ScalarFunc::Time)), "unicode" => Ok(Func::Scalar(ScalarFunc::Unicode)), "quote" => Ok(Func::Scalar(ScalarFunc::Quote)), + "sqlite_version" => Ok(Func::Scalar(ScalarFunc::SqliteVersion)), "json" => Ok(Func::Json(JsonFunc::JSON)), "unixepoch" => Ok(Func::Scalar(ScalarFunc::UnixEpoch)), _ => Err(()), diff --git a/core/lib.rs b/core/lib.rs index 164b299c8..0b899aad8 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -19,7 +19,7 @@ use log::trace; use schema::Schema; use sqlite3_parser::ast; use sqlite3_parser::{ast::Cmd, lexer::sql::Parser}; -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use std::{cell::RefCell, rc::Rc}; #[cfg(feature = "fs")] use storage::database::FileStorage; @@ -42,6 +42,8 @@ pub use storage::pager::Page; pub use storage::wal::Wal; pub use types::Value; +pub static DATABASE_VERSION: OnceLock = OnceLock::new(); + pub struct Database { pager: Rc, schema: Rc, @@ -64,6 +66,10 @@ impl Database { wal: Rc, ) -> Result { let db_header = Pager::begin_open(page_io.clone())?; + DATABASE_VERSION.get_or_init(|| { + let version = db_header.borrow().version_number.clone(); + version.to_string() + }); io.run_once()?; let pager = Rc::new(Pager::finish_open( db_header.clone(), diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 4f4cd2f1a..564872ac2 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -84,7 +84,7 @@ pub struct DatabaseHeader { application_id: u32, reserved: [u8; 20], version_valid_for: u32, - version_number: u32, + pub version_number: u32, } #[derive(Debug, Default)] diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 7324de612..58630d26c 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1311,6 +1311,26 @@ pub fn translate_expr( Ok(target_register) } + ScalarFunc::SqliteVersion => { + if args.is_some() { + crate::bail_parse_error!("sqlite_version function with arguments"); + } + + let output_register = program.alloc_register(); + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: output_register, + dest: output_register, + func: func_ctx, + }); + + program.emit_insn(Insn::Copy { + src_reg: output_register, + dst_reg: target_register, + amount: 1, + }); + Ok(target_register) + } } } } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 9b2fc7d1b..b806c4df1 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -31,7 +31,7 @@ use crate::schema::Table; use crate::storage::sqlite3_ondisk::DatabaseHeader; use crate::storage::{btree::BTreeCursor, pager::Pager}; use crate::types::{AggContext, Cursor, CursorResult, OwnedRecord, OwnedValue, Record}; -use crate::Result; +use crate::{Result, DATABASE_VERSION}; use datetime::{exec_date, exec_time, exec_unixepoch}; @@ -1574,6 +1574,12 @@ impl Program { } } } + ScalarFunc::SqliteVersion => { + let version_integer: i64 = + DATABASE_VERSION.get().unwrap().parse()?; + let version = execute_sqlite_version(version_integer); + state.registers[*dest] = OwnedValue::Text(Rc::new(version)); + } }, crate::function::Func::Agg(_) => { unreachable!("Aggregate functions should not be handled here") @@ -2150,13 +2156,21 @@ fn exec_if(reg: &OwnedValue, null_reg: &OwnedValue, not: bool) -> bool { } } +fn execute_sqlite_version(version_integer: i64) -> String { + let major = version_integer / 1_000_000; + let minor = (version_integer % 1_000_000) / 1_000; + let release = version_integer % 1_000; + + format!("{}.{}.{}", major, minor, release) +} + #[cfg(test)] mod tests { use super::{ exec_abs, exec_char, exec_if, exec_length, exec_like, exec_lower, exec_ltrim, exec_minmax, exec_nullif, exec_quote, exec_random, exec_round, exec_rtrim, exec_sign, exec_substring, - exec_trim, exec_unicode, exec_upper, get_new_rowid, Cursor, CursorResult, LimboError, - OwnedRecord, OwnedValue, Result, + exec_trim, exec_unicode, exec_upper, execute_sqlite_version, get_new_rowid, Cursor, + CursorResult, LimboError, OwnedRecord, OwnedValue, Result, }; use mockall::{mock, predicate}; use rand::{rngs::mock::StepRng, thread_rng}; @@ -2751,4 +2765,11 @@ mod tests { let expected = Some(OwnedValue::Null); assert_eq!(exec_sign(&input), expected); } + + #[test] + fn test_execute_sqlite_version() { + let version_integer = 3046001; + let expected = "3.46.1"; + assert_eq!(execute_sqlite_version(version_integer), expected); + } } diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index 79c622173..853c0b824 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -430,3 +430,7 @@ do_execsql_test sign-text-non-numeric { do_execsql_test sign-null { SELECT sign(NULL); } {} + +do_execsql_test sqlite_version { + SELECT sqlite_version(); +} {3.46.1}