diff --git a/COMPAT.md b/COMPAT.md index 1da0e2faa..abfae6006 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -111,7 +111,7 @@ This document describes the SQLite compatibility status of Limbo: | total_changes() | No | | | trim(X) | Yes | | | trim(X,Y) | Yes | | -| typeof(X) | No | | +| typeof(X) | Yes | | | unhex(X) | No | | | unhex(X,Y) | No | | | unicode(X) | Yes | | diff --git a/core/function.rs b/core/function.rs index cad6e4fe9..74daaf941 100644 --- a/core/function.rs +++ b/core/function.rs @@ -71,6 +71,7 @@ pub enum ScalarFunc { Substring, Date, Time, + Typeof, Unicode, Quote, SqliteVersion, @@ -104,6 +105,7 @@ impl Display for ScalarFunc { ScalarFunc::Substring => "substring".to_string(), ScalarFunc::Date => "date".to_string(), ScalarFunc::Time => "time".to_string(), + ScalarFunc::Typeof => "typeof".to_string(), ScalarFunc::Unicode => "unicode".to_string(), ScalarFunc::Quote => "quote".to_string(), ScalarFunc::SqliteVersion => "sqlite_version".to_string(), @@ -171,6 +173,7 @@ impl Func { "substring" => Ok(Func::Scalar(ScalarFunc::Substring)), "date" => Ok(Func::Scalar(ScalarFunc::Date)), "time" => Ok(Func::Scalar(ScalarFunc::Time)), + "typeof" => Ok(Func::Scalar(ScalarFunc::Typeof)), "unicode" => Ok(Func::Scalar(ScalarFunc::Unicode)), "quote" => Ok(Func::Scalar(ScalarFunc::Quote)), "sqlite_version" => Ok(Func::Scalar(ScalarFunc::SqliteVersion)), diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 667619d75..724cedb83 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1003,6 +1003,7 @@ pub fn translate_expr( | ScalarFunc::Lower | ScalarFunc::Upper | ScalarFunc::Length + | ScalarFunc::Typeof | ScalarFunc::Unicode | ScalarFunc::Quote | ScalarFunc::Sign => { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 0f0ab7396..b50c30b7b 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1518,6 +1518,7 @@ impl Program { | ScalarFunc::Lower | ScalarFunc::Upper | ScalarFunc::Length + | ScalarFunc::Typeof | ScalarFunc::Unicode | ScalarFunc::Quote | ScalarFunc::Sign => { @@ -1528,6 +1529,7 @@ impl Program { ScalarFunc::Lower => exec_lower(reg_value), ScalarFunc::Upper => exec_upper(reg_value), ScalarFunc::Length => Some(exec_length(reg_value)), + ScalarFunc::Typeof => Some(exec_typeof(reg_value)), ScalarFunc::Unicode => Some(exec_unicode(reg_value)), ScalarFunc::Quote => Some(exec_quote(reg_value)), _ => unreachable!(), @@ -2132,6 +2134,18 @@ fn exec_substring( } } +fn exec_typeof(reg: &OwnedValue) -> OwnedValue { + match reg { + OwnedValue::Null => OwnedValue::Text(Rc::new("null".to_string())), + OwnedValue::Integer(_) => OwnedValue::Text(Rc::new("integer".to_string())), + OwnedValue::Float(_) => OwnedValue::Text(Rc::new("real".to_string())), + OwnedValue::Text(_) => OwnedValue::Text(Rc::new("text".to_string())), + OwnedValue::Blob(_) => OwnedValue::Text(Rc::new("blob".to_string())), + OwnedValue::Agg(ctx) => exec_typeof(ctx.final_value()), + OwnedValue::Record(_) => unimplemented!(), + } +} + fn exec_unicode(reg: &OwnedValue) -> OwnedValue { match reg { OwnedValue::Text(_) @@ -2249,11 +2263,12 @@ fn execute_sqlite_version(version_integer: i64) -> String { #[cfg(test)] 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_sign, exec_substring, - exec_trim, exec_unicode, exec_upper, execute_sqlite_version, get_new_rowid, Cursor, - CursorResult, LimboError, OwnedRecord, OwnedValue, Result, + exec_trim, exec_typeof, exec_unicode, exec_upper, execute_sqlite_version, get_new_rowid, + AggContext, Cursor, CursorResult, LimboError, OwnedRecord, OwnedValue, Result, }; use mockall::{mock, predicate}; use rand::{rngs::mock::StepRng, thread_rng}; @@ -2431,6 +2446,33 @@ mod tests { assert_eq!(exec_quote(&input), expected); } + #[test] + fn test_typeof() { + let input = OwnedValue::Null; + let expected: OwnedValue = OwnedValue::Text(Rc::new("null".to_string())); + assert_eq!(exec_typeof(&input), expected); + + let input = OwnedValue::Integer(123); + let expected: OwnedValue = OwnedValue::Text(Rc::new("integer".to_string())); + assert_eq!(exec_typeof(&input), expected); + + let input = OwnedValue::Float(123.456); + let expected: OwnedValue = OwnedValue::Text(Rc::new("real".to_string())); + assert_eq!(exec_typeof(&input), expected); + + let input = OwnedValue::Text(Rc::new("hello".to_string())); + let expected: OwnedValue = OwnedValue::Text(Rc::new("text".to_string())); + assert_eq!(exec_typeof(&input), expected); + + let input = OwnedValue::Blob(Rc::new("limbo".as_bytes().to_vec())); + let expected: OwnedValue = OwnedValue::Text(Rc::new("blob".to_string())); + assert_eq!(exec_typeof(&input), expected); + + let input = OwnedValue::Agg(Box::new(AggContext::Sum(OwnedValue::Integer(123)))); + let expected = OwnedValue::Text(Rc::new("integer".to_string())); + assert_eq!(exec_typeof(&input), expected); + } + #[test] fn test_unicode() { assert_eq!( diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index 79c622173..3d29935b6 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -347,6 +347,51 @@ do_execsql_test substring-2-args-exceed-length { SELECT substring('limbo', 10); } {} +do_execsql_test typeof-null { + SELECT typeof(null); +} {null} + +do_execsql_test typeof-null-case { + SELECT typeof(nuLL); +} {null} + +do_execsql_test typeof-text { + SELECT typeof('hello'); +} {text} + +do_execsql_test typeof-text-empty { + SELECT typeof(''); +} {text} + +do_execsql_test typeof-integer { + SELECT typeof(123); +} {integer} + +do_execsql_test typeof-real { + SELECT typeof(1.0); +} {real} + +# TODO: Uncomment when blobs are better supported +# do_execsql_test typeof-blob { +# SELECT typeof(x'61'); +# } {blob} +# +# do_execsql_test typeof-blob-empty { +# SELECT typeof(x''); +# } {blob} + +do_execsql_test typeof-sum-integer { + SELECT typeof(sum(age)) from users; +} {integer} + +do_execsql_test typeof-sum-real { + SELECT typeof(sum(price)) from products; +} {real} + +do_execsql_test typeof-group_concat { + SELECT typeof(group_concat(name)) from products; +} {text} + do_execsql_test unicode-a { SELECT unicode('a'); } {97}