Merge 'Add typeof(X) scalar function' from Lauri Virtanen

See SQLite documentation:
https://www.sqlite.org/lang_corefunc.html#typeof
Relates to issue #144 Scalar function support

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #310
This commit is contained in:
jussisaurio
2024-09-21 18:03:51 +03:00
5 changed files with 94 additions and 3 deletions

View File

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

View File

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

View File

@@ -1003,6 +1003,7 @@ pub fn translate_expr(
| ScalarFunc::Lower
| ScalarFunc::Upper
| ScalarFunc::Length
| ScalarFunc::Typeof
| ScalarFunc::Unicode
| ScalarFunc::Quote
| ScalarFunc::Sign => {

View File

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

View File

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