diff --git a/COMPAT.md b/COMPAT.md index 7ece6b1c4..4b306c2c8 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -97,7 +97,7 @@ This document describes the SQLite compatibility status of Limbo: | round(X,Y) | Yes | | | rtrim(X) | Yes | | | rtrim(X,Y) | Yes | | -| sign(X) | No | | +| sign(X) | Yes | | | soundex(X) | No | | | sqlite_compileoption_get(N) | No | | | sqlite_compileoption_used(X) | No | | diff --git a/core/function.rs b/core/function.rs index 6a5a0036c..2cbd6166c 100644 --- a/core/function.rs +++ b/core/function.rs @@ -58,6 +58,7 @@ pub enum ScalarFunc { Min, Max, Nullif, + Sign, Substr, Substring, Date, @@ -88,6 +89,7 @@ impl ToString for ScalarFunc { ScalarFunc::Min => "min".to_string(), ScalarFunc::Max => "max".to_string(), ScalarFunc::Nullif => "nullif".to_string(), + ScalarFunc::Sign => "sign".to_string(), ScalarFunc::Substr => "substr".to_string(), ScalarFunc::Substring => "substring".to_string(), ScalarFunc::Date => "date".to_string(), @@ -151,6 +153,7 @@ impl Func { "rtrim" => Ok(Func::Scalar(ScalarFunc::RTrim)), "round" => Ok(Func::Scalar(ScalarFunc::Round)), "length" => Ok(Func::Scalar(ScalarFunc::Length)), + "sign" => Ok(Func::Scalar(ScalarFunc::Sign)), "substr" => Ok(Func::Scalar(ScalarFunc::Substr)), "substring" => Ok(Func::Scalar(ScalarFunc::Substring)), "date" => Ok(Func::Scalar(ScalarFunc::Date)), diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 54695f5a6..7324de612 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -986,7 +986,8 @@ pub fn translate_expr( | ScalarFunc::Upper | ScalarFunc::Length | ScalarFunc::Unicode - | ScalarFunc::Quote => { + | ScalarFunc::Quote + | ScalarFunc::Sign => { let args = if let Some(args) = args { if args.len() != 1 { crate::bail_parse_error!( diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index be3856fdc..9b2fc7d1b 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1464,9 +1464,11 @@ impl Program { | ScalarFunc::Upper | ScalarFunc::Length | ScalarFunc::Unicode - | ScalarFunc::Quote => { + | ScalarFunc::Quote + | ScalarFunc::Sign => { let reg_value = state.registers[*start_reg].borrow_mut(); let result = match scalar_func { + ScalarFunc::Sign => exec_sign(reg_value), ScalarFunc::Abs => exec_abs(reg_value), ScalarFunc::Lower => exec_lower(reg_value), ScalarFunc::Upper => exec_upper(reg_value), @@ -1862,6 +1864,45 @@ fn exec_concat_ws(registers: &[OwnedValue]) -> OwnedValue { OwnedValue::Text(Rc::new(result)) } +fn exec_sign(reg: &OwnedValue) -> Option { + let num = match reg { + OwnedValue::Integer(i) => *i as f64, + OwnedValue::Float(f) => *f, + OwnedValue::Text(s) => { + if let Ok(i) = s.parse::() { + i as f64 + } else if let Ok(f) = s.parse::() { + f + } else { + return Some(OwnedValue::Null); + } + } + OwnedValue::Blob(b) => match std::str::from_utf8(b) { + Ok(s) => { + if let Ok(i) = s.parse::() { + i as f64 + } else if let Ok(f) = s.parse::() { + f + } else { + return Some(OwnedValue::Null); + } + } + Err(_) => return Some(OwnedValue::Null), + }, + _ => return Some(OwnedValue::Null), + }; + + let sign = if num > 0.0 { + 1 + } else if num < 0.0 { + -1 + } else { + 0 + }; + + Some(OwnedValue::Integer(sign)) +} + fn exec_abs(reg: &OwnedValue) -> Option { match reg { OwnedValue::Integer(x) => { @@ -2113,9 +2154,9 @@ fn exec_if(reg: &OwnedValue, null_reg: &OwnedValue, not: bool) -> bool { 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_substring, exec_trim, - exec_unicode, exec_upper, get_new_rowid, Cursor, CursorResult, LimboError, OwnedRecord, - OwnedValue, Result, + 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, }; use mockall::{mock, predicate}; use rand::{rngs::mock::StepRng, thread_rng}; @@ -2643,4 +2684,71 @@ mod tests { expected_val ); } + + #[test] + fn test_exec_sign() { + let input = OwnedValue::Integer(42); + let expected = Some(OwnedValue::Integer(1)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Integer(-42); + let expected = Some(OwnedValue::Integer(-1)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Integer(0); + let expected = Some(OwnedValue::Integer(0)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Float(0.0); + let expected = Some(OwnedValue::Integer(0)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Float(0.1); + let expected = Some(OwnedValue::Integer(1)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Float(42.0); + let expected = Some(OwnedValue::Integer(1)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Float(-42.0); + let expected = Some(OwnedValue::Integer(-1)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Text(Rc::new("abc".to_string())); + let expected = Some(OwnedValue::Null); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Text(Rc::new("42".to_string())); + let expected = Some(OwnedValue::Integer(1)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Text(Rc::new("-42".to_string())); + let expected = Some(OwnedValue::Integer(-1)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Text(Rc::new("0".to_string())); + let expected = Some(OwnedValue::Integer(0)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Blob(Rc::new(b"abc".to_vec())); + let expected = Some(OwnedValue::Null); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Blob(Rc::new(b"42".to_vec())); + let expected = Some(OwnedValue::Integer(1)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Blob(Rc::new(b"-42".to_vec())); + let expected = Some(OwnedValue::Integer(-1)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Blob(Rc::new(b"0".to_vec())); + let expected = Some(OwnedValue::Integer(0)); + assert_eq!(exec_sign(&input), expected); + + let input = OwnedValue::Null; + let expected = Some(OwnedValue::Null); + assert_eq!(exec_sign(&input), expected); + } } diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index d73434ea1..79c622173 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -386,3 +386,47 @@ do_execsql_test quote-null { do_execsql_test quote-integer { SELECT quote(123) } {123} + +do_execsql_test sign-positive-integer { + SELECT sign(42); +} {1} + +do_execsql_test sign-negative-integer { + SELECT sign(-42); +} {-1} + +do_execsql_test sign-zero { + SELECT sign(0); +} {0} + +do_execsql_test sign-positive-float { + SELECT sign(42.0); +} {1} + +do_execsql_test sign-negative-float { + SELECT sign(-42.0); +} {-1} + +do_execsql_test sign-zero-float { + SELECT sign(0.0); +} {0} + +do_execsql_test sign-text-positive-integer { + SELECT sign('42'); +} {1} + +do_execsql_test sign-text-negative-integer { + SELECT sign('-42'); +} {-1} + +do_execsql_test sign-text-zero { + SELECT sign('0'); +} {0} + +do_execsql_test sign-text-non-numeric { + SELECT sign('abc'); +} {} + +do_execsql_test sign-null { + SELECT sign(NULL); +} {}