From f612ead8a38631346f91e68968594a6b879355ea Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 29 Sep 2024 23:33:18 +0300 Subject: [PATCH] Add `zeroblob(N)` scalar function Relates to issue #144 --- COMPAT.md | 2 +- core/function.rs | 3 ++ core/translate/expr.rs | 3 +- core/vdbe/mod.rs | 55 +++++++++++++++++++++++++++++++++-- testing/scalar-functions.test | 20 +++++++++++++ 5 files changed, 79 insertions(+), 4 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index 730458614..177c79215 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -117,7 +117,7 @@ This document describes the SQLite compatibility status of Limbo: | unicode(X) | Yes | | | unlikely(X) | No | | | upper(X) | Yes | | -| zeroblob(N) | No | | +| zeroblob(N) | Yes | | ### Aggregate functions diff --git a/core/function.rs b/core/function.rs index f09ab6ef5..9de850de9 100644 --- a/core/function.rs +++ b/core/function.rs @@ -77,6 +77,7 @@ pub enum ScalarFunc { SqliteVersion, UnixEpoch, Hex, + ZeroBlob, } impl Display for ScalarFunc { @@ -112,6 +113,7 @@ impl Display for ScalarFunc { ScalarFunc::SqliteVersion => "sqlite_version".to_string(), ScalarFunc::UnixEpoch => "unixepoch".to_string(), ScalarFunc::Hex => "hex".to_string(), + ScalarFunc::ZeroBlob => "zeroblob".to_string(), }; write!(f, "{}", str) } @@ -182,6 +184,7 @@ impl Func { "json" => Ok(Func::Json(JsonFunc::Json)), "unixepoch" => Ok(Func::Scalar(ScalarFunc::UnixEpoch)), "hex" => Ok(Func::Scalar(ScalarFunc::Hex)), + "zeroblob" => Ok(Func::Scalar(ScalarFunc::ZeroBlob)), _ => Err(()), } } diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 2cb41e48a..7f251cda1 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1006,7 +1006,8 @@ pub fn translate_expr( | ScalarFunc::Typeof | ScalarFunc::Unicode | ScalarFunc::Quote - | ScalarFunc::Sign => { + | ScalarFunc::Sign + | ScalarFunc::ZeroBlob => { 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 a09bcf7cc..6a6829943 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1531,7 +1531,8 @@ impl Program { | ScalarFunc::Typeof | ScalarFunc::Unicode | ScalarFunc::Quote - | ScalarFunc::Sign => { + | ScalarFunc::Sign + | ScalarFunc::ZeroBlob => { let reg_value = state.registers[*start_reg].borrow_mut(); let result = match scalar_func { ScalarFunc::Sign => exec_sign(reg_value), @@ -1542,6 +1543,7 @@ impl Program { ScalarFunc::Typeof => Some(exec_typeof(reg_value)), ScalarFunc::Unicode => Some(exec_unicode(reg_value)), ScalarFunc::Quote => Some(exec_quote(reg_value)), + ScalarFunc::ZeroBlob => Some(exec_zeroblob(reg_value)), _ => unreachable!(), }; state.registers[*dest] = result.unwrap_or(OwnedValue::Null); @@ -2264,6 +2266,16 @@ fn exec_rtrim(reg: &OwnedValue, pattern: Option) -> OwnedValue { } } +fn exec_zeroblob(req: &OwnedValue) -> OwnedValue { + let length: i64 = match req { + OwnedValue::Integer(i) => *i, + OwnedValue::Float(f) => *f as i64, + OwnedValue::Text(s) => s.parse().unwrap_or(0), + _ => 0, + }; + OwnedValue::Blob(Rc::new(vec![0; length.max(0) as usize])) +} + // exec_if returns whether you should jump fn exec_if(reg: &OwnedValue, null_reg: &OwnedValue, not: bool) -> bool { match reg { @@ -2292,7 +2304,7 @@ mod tests { use super::{ exec_abs, exec_char, exec_hex, exec_if, exec_length, exec_like, exec_lower, exec_ltrim, exec_max, exec_min, exec_nullif, exec_quote, exec_random, exec_round, exec_rtrim, - exec_sign, exec_substring, exec_trim, exec_typeof, exec_unicode, exec_upper, + exec_sign, exec_substring, exec_trim, exec_typeof, exec_unicode, exec_upper, exec_zeroblob, execute_sqlite_version, get_new_rowid, AggContext, Cursor, CursorResult, LimboError, OwnedRecord, OwnedValue, Result, }; @@ -2925,6 +2937,45 @@ mod tests { assert_eq!(exec_sign(&input), expected); } + #[test] + fn test_exec_zeroblob() { + let input = OwnedValue::Integer(0); + let expected = OwnedValue::Blob(Rc::new(vec![])); + assert_eq!(exec_zeroblob(&input), expected); + + let input = OwnedValue::Null; + let expected = OwnedValue::Blob(Rc::new(vec![])); + assert_eq!(exec_zeroblob(&input), expected); + + let input = OwnedValue::Integer(4); + let expected = OwnedValue::Blob(Rc::new(vec![0; 4])); + assert_eq!(exec_zeroblob(&input), expected); + + let input = OwnedValue::Integer(-1); + let expected = OwnedValue::Blob(Rc::new(vec![])); + assert_eq!(exec_zeroblob(&input), expected); + + let input = OwnedValue::Text(Rc::new("5".to_string())); + let expected = OwnedValue::Blob(Rc::new(vec![0; 5])); + assert_eq!(exec_zeroblob(&input), expected); + + let input = OwnedValue::Text(Rc::new("-5".to_string())); + let expected = OwnedValue::Blob(Rc::new(vec![])); + assert_eq!(exec_zeroblob(&input), expected); + + let input = OwnedValue::Text(Rc::new("text".to_string())); + let expected = OwnedValue::Blob(Rc::new(vec![])); + assert_eq!(exec_zeroblob(&input), expected); + + let input = OwnedValue::Float(2.6); + let expected = OwnedValue::Blob(Rc::new(vec![0; 2])); + assert_eq!(exec_zeroblob(&input), expected); + + let input = OwnedValue::Blob(Rc::new(vec![1])); + let expected = OwnedValue::Blob(Rc::new(vec![])); + assert_eq!(exec_zeroblob(&input), expected); + } + #[test] fn test_execute_sqlite_version() { let version_integer = 3046001; diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index 28d8e9200..32d518352 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -514,3 +514,23 @@ do_execsql_test sign-text-non-numeric { do_execsql_test sign-null { SELECT sign(NULL); } {} + +do_execsql_test zeroblob-int-0 { + SELECT zeroblob(0) = x''; +} {1} + +do_execsql_test zeroblob-int-1 { + SELECT zeroblob(1) = x'00'; +} {1} + +do_execsql_test zeroblob-str-3 { + SELECT zeroblob('3') = x'000000'; +} {1} + +do_execsql_test zeroblob-str-a { + SELECT zeroblob('a') = x''; +} {1} + +do_execsql_test zeroblob-blob { + SELECT zeroblob(x'01') = x''; +} {1}