From a9b94be0d1e3562d2ab3ebbce7dee4e10a54c217 Mon Sep 17 00:00:00 2001 From: Matthew Ma Date: Tue, 31 Dec 2024 06:50:24 -0800 Subject: [PATCH] Stub clock Makes clock deterministic for test scenarios. --- core/vdbe/builder.rs | 3 ++- core/vdbe/clock.rs | 21 ++++++++++++++++++++ core/vdbe/datetime.rs | 45 ++++++++++++++++++++++++++----------------- core/vdbe/mod.rs | 18 ++++++++++++----- 4 files changed, 63 insertions(+), 24 deletions(-) create mode 100644 core/vdbe/clock.rs diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index 561765738..6d85f1653 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -4,7 +4,7 @@ use std::{ rc::{Rc, Weak}, }; -use crate::{storage::sqlite3_ondisk::DatabaseHeader, Connection}; +use crate::{storage::sqlite3_ondisk::DatabaseHeader, vdbe::clock::Clock, Connection}; use super::{BranchOffset, CursorID, Insn, InsnReference, Program, Table}; @@ -390,6 +390,7 @@ impl ProgramBuilder { comments: self.comments, connection, auto_commit: true, + clock: Clock::Real, } } } diff --git a/core/vdbe/clock.rs b/core/vdbe/clock.rs new file mode 100644 index 000000000..d81f06e53 --- /dev/null +++ b/core/vdbe/clock.rs @@ -0,0 +1,21 @@ +use chrono::{DateTime, Utc}; +use std::time::UNIX_EPOCH; + +#[derive(Debug)] +pub enum Clock { + Sim(DateTime), + Real, +} + +impl Clock { + pub fn sim_clock() -> Self { + Clock::Sim(UNIX_EPOCH.into()) + } + + pub fn now(&self) -> DateTime { + match self { + Clock::Sim(now) => *now, + Clock::Real => chrono::Local::now().to_utc(), + } + } +} diff --git a/core/vdbe/datetime.rs b/core/vdbe/datetime.rs index 7b9e49fd6..57f8b9e18 100644 --- a/core/vdbe/datetime.rs +++ b/core/vdbe/datetime.rs @@ -4,14 +4,15 @@ use chrono::{ use std::rc::Rc; use crate::types::OwnedValue; +use crate::vdbe::Clock; use crate::LimboError::InvalidModifier; use crate::Result; /// Implementation of the date() SQL function. -pub fn exec_date(values: &[OwnedValue]) -> OwnedValue { +pub fn exec_date(values: &[OwnedValue], clock: &Clock) -> OwnedValue { let maybe_dt = match values.first() { - None => parse_naive_date_time(&OwnedValue::build_text(Rc::new("now".to_string()))), - Some(value) => parse_naive_date_time(value), + None => parse_naive_date_time(&OwnedValue::build_text(Rc::new("now".to_string())), clock), + Some(value) => parse_naive_date_time(value, clock), }; // early return, no need to look at modifiers if result invalid if maybe_dt.is_none() { @@ -34,10 +35,10 @@ pub fn exec_date(values: &[OwnedValue]) -> OwnedValue { } /// Implementation of the time() SQL function. -pub fn exec_time(time_value: &[OwnedValue]) -> OwnedValue { +pub fn exec_time(time_value: &[OwnedValue], clock: &Clock) -> OwnedValue { let maybe_dt = match time_value.first() { - None => parse_naive_date_time(&OwnedValue::build_text(Rc::new("now".to_string()))), - Some(value) => parse_naive_date_time(value), + None => parse_naive_date_time(&OwnedValue::build_text(Rc::new("now".to_string())), clock), + Some(value) => parse_naive_date_time(value, clock), }; // early return, no need to look at modifiers if result invalid if maybe_dt.is_none() { @@ -111,8 +112,8 @@ fn apply_modifier(dt: &mut NaiveDateTime, modifier: &str) -> Result<()> { Ok(()) } -pub fn exec_unixepoch(time_value: &OwnedValue) -> Result { - let dt = parse_naive_date_time(time_value); +pub fn exec_unixepoch(time_value: &OwnedValue, clock: &Clock) -> Result { + let dt = parse_naive_date_time(time_value, clock); match dt { Some(dt) => Ok(get_unixepoch_from_naive_datetime(dt)), None => Ok(String::new()), @@ -123,16 +124,16 @@ fn get_unixepoch_from_naive_datetime(value: NaiveDateTime) -> String { value.and_utc().timestamp().to_string() } -fn parse_naive_date_time(time_value: &OwnedValue) -> Option { +fn parse_naive_date_time(time_value: &OwnedValue, clock: &Clock) -> Option { match time_value { - OwnedValue::Text(s) => get_date_time_from_time_value_string(&s.value), + OwnedValue::Text(s) => get_date_time_from_time_value_string(&s.value, clock), OwnedValue::Integer(i) => get_date_time_from_time_value_integer(*i), OwnedValue::Float(f) => get_date_time_from_time_value_float(*f), _ => None, } } -fn get_date_time_from_time_value_string(value: &str) -> Option { +fn get_date_time_from_time_value_string(value: &str, clock: &Clock) -> Option { // Time-value formats: // 1-7. YYYY-MM-DD[THH:MM[:SS[.SSS]]] // 8-10. HH:MM[:SS[.SSS]] @@ -143,7 +144,7 @@ fn get_date_time_from_time_value_string(value: &str) -> Option { // Check for 'now' if value.trim().eq_ignore_ascii_case("now") { - return Some(chrono::Local::now().to_utc().naive_utc()); + return Some(clock.now().naive_utc()); } // Check for Julian day number (integer or float) @@ -401,7 +402,8 @@ mod tests { #[test] fn test_valid_get_date_from_time_value() { - let now = chrono::Local::now().to_utc().format("%Y-%m-%d").to_string(); + let clock = Clock::sim_clock(); + let now = clock.now().format("%Y-%m-%d").to_string(); let prev_date_str = "2024-07-20"; let test_date_str = "2024-07-21"; @@ -609,8 +611,10 @@ mod tests { (OwnedValue::Integer(2460513), test_date_str), ]; + let clock = Clock::sim_clock(); + for (input, expected) in test_cases { - let result = exec_date(&[input.clone()]); + let result = exec_date(&[input.clone()], &clock); assert_eq!( result, OwnedValue::build_text(Rc::new(expected.to_string())), @@ -650,8 +654,10 @@ mod tests { OwnedValue::build_text(Rc::new("2024-07-21T12:00:00UTC".to_string())), // Named timezone (not supported) ]; + let clock = Clock::sim_clock(); + for case in invalid_cases.iter() { - let result = exec_date(&[case.clone()]); + let result = exec_date(&[case.clone()], &clock); match result { OwnedValue::Text(ref result_str) if result_str.value.is_empty() => (), _ => panic!( @@ -664,7 +670,8 @@ mod tests { #[test] fn test_valid_get_time_from_datetime_value() { - let now = chrono::Local::now().to_utc().format("%H:%M:%S").to_string(); + let clock = Clock::sim_clock(); + let now = clock.now().format("%H:%M:%S").to_string(); let test_time_str = "22:30:45"; let prev_time_str = "20:30:45"; @@ -837,7 +844,7 @@ mod tests { ]; for (input, expected) in test_cases { - let result = exec_time(&[input]); + let result = exec_time(&[input], &clock); if let OwnedValue::Text(result_str) = result { assert_eq!(result_str.value.as_str(), expected); } else { @@ -876,8 +883,10 @@ mod tests { OwnedValue::build_text(Rc::new("2024-07-21T12:00:00UTC".to_string())), // Named timezone (not supported) ]; + let clock = Clock::sim_clock(); + for case in invalid_cases { - let result = exec_time(&[case.clone()]); + let result = exec_time(&[case.clone()], &clock); match result { OwnedValue::Text(ref result_str) if result_str.value.is_empty() => (), _ => panic!( diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 31193e604..5f8c3be23 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -18,6 +18,7 @@ //! https://www.sqlite.org/opcode.html pub mod builder; +mod clock; mod datetime; pub mod explain; pub mod insn; @@ -41,6 +42,7 @@ use crate::vdbe::insn::Insn; #[cfg(feature = "json")] use crate::{function::JsonFunc, json::get_json, json::json_array, json::json_array_length}; use crate::{Connection, Result, Rows, TransactionState, DATABASE_VERSION}; +use clock::Clock; use datetime::{exec_date, exec_time, exec_unixepoch}; use insn::{ exec_add, exec_bit_and, exec_bit_not, exec_bit_or, exec_divide, exec_multiply, exec_remainder, @@ -151,6 +153,7 @@ pub struct Program { pub comments: HashMap, pub connection: Weak, pub auto_commit: bool, + pub clock: Clock, } impl Program { @@ -1520,25 +1523,30 @@ impl Program { state.registers[*dest] = result; } ScalarFunc::Date => { - let result = - exec_date(&state.registers[*start_reg..*start_reg + arg_count]); + let result = exec_date( + &state.registers[*start_reg..*start_reg + arg_count], + &self.clock, + ); state.registers[*dest] = result; } ScalarFunc::Time => { - let result = - exec_time(&state.registers[*start_reg..*start_reg + arg_count]); + let result = exec_time( + &state.registers[*start_reg..*start_reg + arg_count], + &self.clock, + ); state.registers[*dest] = result; } ScalarFunc::UnixEpoch => { if *start_reg == 0 { let unixepoch: String = exec_unixepoch( &OwnedValue::build_text(Rc::new("now".to_string())), + &self.clock, )?; state.registers[*dest] = OwnedValue::build_text(Rc::new(unixepoch)); } else { let datetime_value = &state.registers[*start_reg]; - let unixepoch = exec_unixepoch(datetime_value); + let unixepoch = exec_unixepoch(datetime_value, &self.clock); match unixepoch { Ok(time) => { state.registers[*dest] =