Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stub clock #586

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion core/vdbe/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -390,6 +390,7 @@ impl ProgramBuilder {
comments: self.comments,
connection,
auto_commit: true,
clock: Clock::Real,
}
}
}
21 changes: 21 additions & 0 deletions core/vdbe/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use chrono::{DateTime, Utc};
use std::time::UNIX_EPOCH;

#[derive(Debug)]
pub enum Clock {
Sim(DateTime<Utc>),
Real,
}

impl Clock {
pub fn sim_clock() -> Self {
Clock::Sim(UNIX_EPOCH.into())
}

pub fn now(&self) -> DateTime<Utc> {
match self {
Clock::Sim(now) => *now,
Clock::Real => chrono::Local::now().to_utc(),
}
}
}
45 changes: 27 additions & 18 deletions core/vdbe/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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() {
Expand Down Expand Up @@ -111,8 +112,8 @@ fn apply_modifier(dt: &mut NaiveDateTime, modifier: &str) -> Result<()> {
Ok(())
}

pub fn exec_unixepoch(time_value: &OwnedValue) -> Result<String> {
let dt = parse_naive_date_time(time_value);
pub fn exec_unixepoch(time_value: &OwnedValue, clock: &Clock) -> Result<String> {
let dt = parse_naive_date_time(time_value, clock);
match dt {
Some(dt) => Ok(get_unixepoch_from_naive_datetime(dt)),
None => Ok(String::new()),
Expand All @@ -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<NaiveDateTime> {
fn parse_naive_date_time(time_value: &OwnedValue, clock: &Clock) -> Option<NaiveDateTime> {
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<NaiveDateTime> {
fn get_date_time_from_time_value_string(value: &str, clock: &Clock) -> Option<NaiveDateTime> {
// Time-value formats:
// 1-7. YYYY-MM-DD[THH:MM[:SS[.SSS]]]
// 8-10. HH:MM[:SS[.SSS]]
Expand All @@ -143,7 +144,7 @@ fn get_date_time_from_time_value_string(value: &str) -> Option<NaiveDateTime> {

// 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)
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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())),
Expand Down Expand Up @@ -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!(
Expand All @@ -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";
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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!(
Expand Down
18 changes: 13 additions & 5 deletions core/vdbe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//! https://www.sqlite.org/opcode.html

pub mod builder;
mod clock;
mod datetime;
pub mod explain;
pub mod insn;
Expand All @@ -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,
Expand Down Expand Up @@ -151,6 +153,7 @@ pub struct Program {
pub comments: HashMap<BranchOffset, &'static str>,
pub connection: Weak<Connection>,
pub auto_commit: bool,
pub clock: Clock,
}

impl Program {
Expand Down Expand Up @@ -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] =
Expand Down
Loading