Merge 'implementation of scalar function sign(X)' from Jean Arhancet

This is related to the issue
https://github.com/penberg/limbo/issues/144. Add the scalar function
`sign(X)`

Closes #328
This commit is contained in:
jussisaurio
2024-09-15 16:02:51 +03:00
5 changed files with 162 additions and 6 deletions

View File

@@ -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 | |

View File

@@ -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)),

View File

@@ -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!(

View File

@@ -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<OwnedValue> {
let num = match reg {
OwnedValue::Integer(i) => *i as f64,
OwnedValue::Float(f) => *f,
OwnedValue::Text(s) => {
if let Ok(i) = s.parse::<i64>() {
i as f64
} else if let Ok(f) = s.parse::<f64>() {
f
} else {
return Some(OwnedValue::Null);
}
}
OwnedValue::Blob(b) => match std::str::from_utf8(b) {
Ok(s) => {
if let Ok(i) = s.parse::<i64>() {
i as f64
} else if let Ok(f) = s.parse::<f64>() {
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<OwnedValue> {
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);
}
}

View File

@@ -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);
} {}