mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-19 01:24:20 +01:00
core: support modifiers in time function
This commit is contained in:
20
COMPAT.md
20
COMPAT.md
@@ -89,7 +89,7 @@ This document describes the SQLite compatibility status of Limbo:
|
|||||||
| nullif(X,Y) | Yes | |
|
| nullif(X,Y) | Yes | |
|
||||||
| octet_length(X) | No | |
|
| octet_length(X) | No | |
|
||||||
| printf(FORMAT,...) | No | |
|
| printf(FORMAT,...) | No | |
|
||||||
| quote(X) | Yes | |
|
| quote(X) | Yes | |
|
||||||
| random() | Yes | |
|
| random() | Yes | |
|
||||||
| randomblob(N) | No | |
|
| randomblob(N) | No | |
|
||||||
| replace(X,Y,Z) | No | |
|
| replace(X,Y,Z) | No | |
|
||||||
@@ -137,15 +137,15 @@ This document describes the SQLite compatibility status of Limbo:
|
|||||||
|
|
||||||
### Date and time functions
|
### Date and time functions
|
||||||
|
|
||||||
| Function | Status | Comment |
|
| Function | Status | Comment |
|
||||||
|------------------------------|---------|------------------------|
|
|------------------------------|---------|------------------------------|
|
||||||
| date() | Partial | |
|
| date() | Partial | |
|
||||||
| time() | Partial | not supported modifier |
|
| time() | Partial | partially supports modifiers |
|
||||||
| datetime() | No | |
|
| datetime() | No | |
|
||||||
| julianday() | No | |
|
| julianday() | No | |
|
||||||
| unixepoch() | No | |
|
| unixepoch() | No | |
|
||||||
| strftime() | No | |
|
| strftime() | No | |
|
||||||
| timediff() | No | |
|
| timediff() | No | |
|
||||||
|
|
||||||
### JSON functions
|
### JSON functions
|
||||||
|
|
||||||
|
|||||||
@@ -945,24 +945,21 @@ pub fn translate_expr(
|
|||||||
Ok(target_register)
|
Ok(target_register)
|
||||||
}
|
}
|
||||||
ScalarFunc::Time => {
|
ScalarFunc::Time => {
|
||||||
let mut start_reg = 0;
|
|
||||||
if let Some(args) = args {
|
if let Some(args) = args {
|
||||||
if args.len() > 1 {
|
for arg in args.iter() {
|
||||||
crate::bail_parse_error!("date function with > 1 arguments. Modifiers are not yet supported.");
|
// register containing result of each argument expression
|
||||||
} else if args.len() == 1 {
|
let target_reg = program.alloc_register();
|
||||||
let arg_reg = program.alloc_register();
|
_ = translate_expr(
|
||||||
let _ = translate_expr(
|
|
||||||
program,
|
program,
|
||||||
referenced_tables,
|
referenced_tables,
|
||||||
&args[0],
|
arg,
|
||||||
arg_reg,
|
target_reg,
|
||||||
cursor_hint,
|
cursor_hint,
|
||||||
)?;
|
)?;
|
||||||
start_reg = arg_reg;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
program.emit_insn(Insn::Function {
|
program.emit_insn(Insn::Function {
|
||||||
start_reg: start_reg,
|
start_reg: target_register + 1,
|
||||||
dest: target_register,
|
dest: target_register,
|
||||||
func: crate::vdbe::Func::Scalar(ScalarFunc::Time),
|
func: crate::vdbe::Func::Scalar(ScalarFunc::Time),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use chrono::{
|
use chrono::{
|
||||||
DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta, TimeZone, Timelike, Utc,
|
DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta, TimeZone, Timelike, Utc,
|
||||||
};
|
};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::types::OwnedValue;
|
use crate::types::OwnedValue;
|
||||||
use crate::LimboError::InvalidModifier;
|
use crate::LimboError::InvalidModifier;
|
||||||
@@ -16,12 +17,29 @@ pub fn exec_date(time_value: &OwnedValue) -> Result<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Implementation of the time() SQL function.
|
/// Implementation of the time() SQL function.
|
||||||
pub fn exec_time(time_value: &OwnedValue) -> Result<String> {
|
pub fn exec_time(time_value: &[OwnedValue]) -> OwnedValue {
|
||||||
let dt = parse_naive_date_time(time_value);
|
let maybe_dt = match time_value.first() {
|
||||||
match dt {
|
None => parse_naive_date_time(&OwnedValue::Text(Rc::new("now".to_string()))),
|
||||||
Some(dt) => Ok(get_time_from_naive_datetime(dt)),
|
Some(value) => parse_naive_date_time(value),
|
||||||
None => Ok(String::new()),
|
};
|
||||||
|
// early return, no need to look at modifiers if result invalid
|
||||||
|
if maybe_dt.is_none() {
|
||||||
|
return OwnedValue::Text(Rc::new(String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apply modifiers if result is valid
|
||||||
|
let mut dt = maybe_dt.unwrap();
|
||||||
|
for modifier in time_value.iter().skip(1) {
|
||||||
|
if let OwnedValue::Text(modifier_str) = modifier {
|
||||||
|
if apply_modifier(&mut dt, modifier_str).is_err() {
|
||||||
|
return OwnedValue::Text(Rc::new(String::new()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return OwnedValue::Text(Rc::new(String::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OwnedValue::Text(Rc::new(get_time_from_naive_datetime(dt)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_modifier(dt: &mut NaiveDateTime, modifier: &str) -> Result<()> {
|
fn apply_modifier(dt: &mut NaiveDateTime, modifier: &str) -> Result<()> {
|
||||||
@@ -630,7 +648,7 @@ mod tests {
|
|||||||
let prev_time_str = "20:30:45";
|
let prev_time_str = "20:30:45";
|
||||||
let next_time_str = "03:30:45";
|
let next_time_str = "03:30:45";
|
||||||
|
|
||||||
let test_cases = [
|
let test_cases = vec![
|
||||||
// Format 1: YYYY-MM-DD (no timezone applicable)
|
// Format 1: YYYY-MM-DD (no timezone applicable)
|
||||||
(
|
(
|
||||||
OwnedValue::Text(Rc::new("2024-07-21".to_string())),
|
OwnedValue::Text(Rc::new("2024-07-21".to_string())),
|
||||||
@@ -791,18 +809,18 @@ mod tests {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (input, expected) in test_cases {
|
for (input, expected) in test_cases {
|
||||||
assert_eq!(
|
let result = exec_time(&[input]);
|
||||||
exec_time(&input).unwrap(),
|
if let OwnedValue::Text(result_str) = result {
|
||||||
expected,
|
assert_eq!(result_str.as_str(), expected);
|
||||||
"Failed for input: {:?}",
|
} else {
|
||||||
input
|
panic!("Expected OwnedValue::Text, but got: {:?}", result);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_get_time_from_datetime_value() {
|
fn test_invalid_get_time_from_datetime_value() {
|
||||||
let invalid_cases = [
|
let invalid_cases = vec![
|
||||||
OwnedValue::Text(Rc::new("2024-07-21 25:00".to_string())), // Invalid hour
|
OwnedValue::Text(Rc::new("2024-07-21 25:00".to_string())), // Invalid hour
|
||||||
OwnedValue::Text(Rc::new("2024-07-21 24:00:00".to_string())), // Invalid hour
|
OwnedValue::Text(Rc::new("2024-07-21 24:00:00".to_string())), // Invalid hour
|
||||||
OwnedValue::Text(Rc::new("2024-07-21 23:60:00".to_string())), // Invalid minute
|
OwnedValue::Text(Rc::new("2024-07-21 23:60:00".to_string())), // Invalid minute
|
||||||
@@ -830,21 +848,15 @@ mod tests {
|
|||||||
OwnedValue::Text(Rc::new("2024-07-21T12:00:00UTC".to_string())), // Named timezone (not supported)
|
OwnedValue::Text(Rc::new("2024-07-21T12:00:00UTC".to_string())), // Named timezone (not supported)
|
||||||
];
|
];
|
||||||
|
|
||||||
for case in invalid_cases.iter() {
|
for case in invalid_cases {
|
||||||
let result = exec_time(case);
|
let result = exec_time(&[case.clone()]);
|
||||||
assert!(
|
match result {
|
||||||
result.is_ok(),
|
OwnedValue::Text(ref result_str) if result_str.is_empty() => (),
|
||||||
"Error encountered while parsing time value {}: {}",
|
_ => panic!(
|
||||||
case,
|
"Expected empty string for input: {:?}, but got: {:?}",
|
||||||
result.unwrap_err()
|
case, result
|
||||||
);
|
),
|
||||||
let result_str = result.unwrap();
|
}
|
||||||
assert!(
|
|
||||||
result_str.is_empty(),
|
|
||||||
"Expected empty string for input: {:?}, but got: {:?}",
|
|
||||||
case,
|
|
||||||
result_str
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1361,25 +1361,8 @@ impl Program {
|
|||||||
state.pc += 1;
|
state.pc += 1;
|
||||||
}
|
}
|
||||||
Func::Scalar(ScalarFunc::Time) => {
|
Func::Scalar(ScalarFunc::Time) => {
|
||||||
if *start_reg == 0 {
|
let result = exec_time(&state.registers[*start_reg..]);
|
||||||
let time_str =
|
state.registers[*dest] = result;
|
||||||
exec_time(&OwnedValue::Text(Rc::new("now".to_string())))?;
|
|
||||||
state.registers[*dest] = OwnedValue::Text(Rc::new(time_str));
|
|
||||||
} else {
|
|
||||||
let datetime_value = &state.registers[*start_reg];
|
|
||||||
let time_str = exec_time(datetime_value);
|
|
||||||
match time_str {
|
|
||||||
Ok(time) => {
|
|
||||||
state.registers[*dest] = OwnedValue::Text(Rc::new(time))
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Err(LimboError::ParseError(format!(
|
|
||||||
"Error encountered while parsing time value: {}",
|
|
||||||
e
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.pc += 1;
|
state.pc += 1;
|
||||||
}
|
}
|
||||||
Func::Scalar(ScalarFunc::Unicode) => {
|
Func::Scalar(ScalarFunc::Unicode) => {
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ source $testdir/like.test
|
|||||||
source $testdir/orderby.test
|
source $testdir/orderby.test
|
||||||
source $testdir/pragma.test
|
source $testdir/pragma.test
|
||||||
source $testdir/scalar-functions.test
|
source $testdir/scalar-functions.test
|
||||||
|
source $testdir/scalar-functions-datetime.test
|
||||||
source $testdir/select.test
|
source $testdir/select.test
|
||||||
source $testdir/where.test
|
source $testdir/where.test
|
||||||
125
testing/scalar-functions-datetime.test
Executable file
125
testing/scalar-functions-datetime.test
Executable file
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env tclsh
|
||||||
|
|
||||||
|
set testdir [file dirname $argv0]
|
||||||
|
source $testdir/tester.tcl
|
||||||
|
|
||||||
|
do_execsql_test time-no-arg {
|
||||||
|
SELECT length(time()) = 8;
|
||||||
|
} {1}
|
||||||
|
|
||||||
|
do_execsql_test time-current-time {
|
||||||
|
SELECT length(time('now')) = 8;
|
||||||
|
} {1}
|
||||||
|
|
||||||
|
do_execsql_test time-specific-time {
|
||||||
|
SELECT time('04:02:00');
|
||||||
|
} {04:02:00}
|
||||||
|
|
||||||
|
do_execsql_test time-of-datetime {
|
||||||
|
SELECT time('2023-05-18 15:30:45');
|
||||||
|
} {15:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-iso8601 {
|
||||||
|
SELECT time('2023-05-18T15:30:45');
|
||||||
|
} {15:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-milliseconds {
|
||||||
|
SELECT time('2023-05-18 15:30:45.123');
|
||||||
|
} {15:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-julian-day-integer {
|
||||||
|
SELECT time(2460082);
|
||||||
|
} {12:00:00}
|
||||||
|
|
||||||
|
do_execsql_test time-julian-day-float {
|
||||||
|
SELECT time(2460082.2);
|
||||||
|
} {16:48:00}
|
||||||
|
|
||||||
|
do_execsql_test time-invalid-input {
|
||||||
|
SELECT time('not a time');
|
||||||
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test time-null-input {
|
||||||
|
SELECT time(NULL);
|
||||||
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test time-out-of-range {
|
||||||
|
SELECT time('25:05:01');
|
||||||
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test time-date-only {
|
||||||
|
SELECT time('2024-02-02');
|
||||||
|
} {00:00:00}
|
||||||
|
|
||||||
|
do_execsql_test time-with-timezone-utc {
|
||||||
|
SELECT time('2023-05-18 15:30:45Z');
|
||||||
|
} {15:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-timezone-positive {
|
||||||
|
SELECT time('2023-05-18 23:30:45+07:00');
|
||||||
|
} {16:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-timezone-negative {
|
||||||
|
SELECT time('2023-05-19 01:30:45-05:00');
|
||||||
|
} {06:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-timezone-day-change-positive {
|
||||||
|
SELECT time('2023-05-18 23:30:45-03:00');
|
||||||
|
} {02:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-timezone-day-change-negative {
|
||||||
|
SELECT time('2023-05-19 01:30:45+03:00');
|
||||||
|
} {22:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-timezone-iso8601 {
|
||||||
|
SELECT time('2023-05-18T15:30:45+02:00');
|
||||||
|
} {13:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-timezone-and-milliseconds {
|
||||||
|
SELECT time('2023-05-18 15:30:45.123+02:00');
|
||||||
|
} {13:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-invalid-timezone {
|
||||||
|
SELECT time('2023-05-18 15:30:45+25:00');
|
||||||
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test time-with-modifier-start-of-day {
|
||||||
|
SELECT time('2023-05-18 15:30:45', 'start of day');
|
||||||
|
} {00:00:00}
|
||||||
|
|
||||||
|
do_execsql_test time-with-modifier-add-hours {
|
||||||
|
SELECT time('2023-05-18 15:30:45', '+2 hours');
|
||||||
|
} {17:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-modifier-add-minutes {
|
||||||
|
SELECT time('2023-05-18 15:30:45', '+45 minutes');
|
||||||
|
} {16:15:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-modifier-add-seconds {
|
||||||
|
SELECT time('2023-05-18 15:30:45', '+30 seconds');
|
||||||
|
} {15:31:15}
|
||||||
|
|
||||||
|
do_execsql_test time-with-modifier-subtract-hours {
|
||||||
|
SELECT time('2023-05-18 15:30:45', '-3 hours');
|
||||||
|
} {12:30:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-modifier-subtract-minutes {
|
||||||
|
SELECT time('2023-05-18 15:30:45', '-15 minutes');
|
||||||
|
} {15:15:45}
|
||||||
|
|
||||||
|
do_execsql_test time-with-modifier-subtract-seconds {
|
||||||
|
SELECT time('2023-05-18 15:30:45', '-45 seconds');
|
||||||
|
} {15:30:00}
|
||||||
|
|
||||||
|
do_execsql_test time-with-multiple-modifiers {
|
||||||
|
SELECT time('2023-05-18 15:30:45', '+1 hours', '-30 minutes', '+15 seconds');
|
||||||
|
} {16:01:00}
|
||||||
|
|
||||||
|
do_execsql_test time-with-invalid-modifier {
|
||||||
|
SELECT time('2023-05-18 15:30:45', 'invalid modifier');
|
||||||
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test time-with-invalid-modifier {
|
||||||
|
SELECT time('2023-05-18 15:30:45', '+1 hour', 'invalid modifier');
|
||||||
|
} {{}}
|
||||||
|
|
||||||
@@ -403,86 +403,6 @@ do_execsql_test date-with-invalid-timezone {
|
|||||||
SELECT date('2023-05-18 15:30:45+25:00');
|
SELECT date('2023-05-18 15:30:45+25:00');
|
||||||
} {{}}
|
} {{}}
|
||||||
|
|
||||||
do_execsql_test time-no-arg {
|
|
||||||
SELECT length(time()) = 8;
|
|
||||||
} {1}
|
|
||||||
|
|
||||||
do_execsql_test time-current-time {
|
|
||||||
SELECT length(time('now')) = 8;
|
|
||||||
} {1}
|
|
||||||
|
|
||||||
do_execsql_test time-specific-time {
|
|
||||||
SELECT time('04:02:00');
|
|
||||||
} {04:02:00}
|
|
||||||
|
|
||||||
do_execsql_test time-of-datetime {
|
|
||||||
SELECT time('2023-05-18 15:30:45');
|
|
||||||
} {15:30:45}
|
|
||||||
|
|
||||||
do_execsql_test time-iso8601 {
|
|
||||||
SELECT time('2023-05-18T15:30:45');
|
|
||||||
} {15:30:45}
|
|
||||||
|
|
||||||
do_execsql_test time-with-milliseconds {
|
|
||||||
SELECT time('2023-05-18 15:30:45.123');
|
|
||||||
} {15:30:45}
|
|
||||||
|
|
||||||
do_execsql_test time-julian-day-integer {
|
|
||||||
SELECT time(2460082);
|
|
||||||
} {12:00:00}
|
|
||||||
|
|
||||||
do_execsql_test time-julian-day-float {
|
|
||||||
SELECT time(2460082.2);
|
|
||||||
} {16:48:00}
|
|
||||||
|
|
||||||
do_execsql_test time-invalid-input {
|
|
||||||
SELECT time('not a time');
|
|
||||||
} {{}}
|
|
||||||
|
|
||||||
do_execsql_test time-null-input {
|
|
||||||
SELECT time(NULL);
|
|
||||||
} {{}}
|
|
||||||
|
|
||||||
do_execsql_test time-out-of-range {
|
|
||||||
SELECT time('25:05:01');
|
|
||||||
} {{}}
|
|
||||||
|
|
||||||
do_execsql_test time-date-only {
|
|
||||||
SELECT time('2024-02-02');
|
|
||||||
} {00:00:00}
|
|
||||||
|
|
||||||
do_execsql_test time-with-timezone-utc {
|
|
||||||
SELECT time('2023-05-18 15:30:45Z');
|
|
||||||
} {15:30:45}
|
|
||||||
|
|
||||||
do_execsql_test time-with-timezone-positive {
|
|
||||||
SELECT time('2023-05-18 23:30:45+07:00');
|
|
||||||
} {16:30:45}
|
|
||||||
|
|
||||||
do_execsql_test time-with-timezone-negative {
|
|
||||||
SELECT time('2023-05-19 01:30:45-05:00');
|
|
||||||
} {06:30:45}
|
|
||||||
|
|
||||||
do_execsql_test time-with-timezone-day-change-positive {
|
|
||||||
SELECT time('2023-05-18 23:30:45-03:00');
|
|
||||||
} {02:30:45}
|
|
||||||
|
|
||||||
do_execsql_test time-with-timezone-day-change-negative {
|
|
||||||
SELECT time('2023-05-19 01:30:45+03:00');
|
|
||||||
} {22:30:45}
|
|
||||||
|
|
||||||
do_execsql_test time-with-timezone-iso8601 {
|
|
||||||
SELECT time('2023-05-18T15:30:45+02:00');
|
|
||||||
} {13:30:45}
|
|
||||||
|
|
||||||
do_execsql_test time-with-timezone-and-milliseconds {
|
|
||||||
SELECT time('2023-05-18 15:30:45.123+02:00');
|
|
||||||
} {13:30:45}
|
|
||||||
|
|
||||||
do_execsql_test time-with-invalid-timezone {
|
|
||||||
SELECT time('2023-05-18 15:30:45+25:00');
|
|
||||||
} {{}}
|
|
||||||
|
|
||||||
do_execsql_test unicode-a {
|
do_execsql_test unicode-a {
|
||||||
SELECT unicode('a');
|
SELECT unicode('a');
|
||||||
} {97}
|
} {97}
|
||||||
|
|||||||
Reference in New Issue
Block a user