diff --git a/COMPAT.md b/COMPAT.md index f30ef91ef..9290103eb 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -91,7 +91,7 @@ This document describes the SQLite compatibility status of Limbo: | printf(FORMAT,...) | No | | | quote(X) | Yes | | | random() | Yes | | -| randomblob(N) | No | | +| randomblob(N) | Yes | | | replace(X,Y,Z) | No | | | round(X) | Yes | | | round(X,Y) | Yes | | diff --git a/core/function.rs b/core/function.rs index 7d4aef210..3eb110007 100644 --- a/core/function.rs +++ b/core/function.rs @@ -58,6 +58,7 @@ pub enum ScalarFunc { Upper, Lower, Random, + RandomBlob, Trim, LTrim, RTrim, @@ -95,6 +96,7 @@ impl Display for ScalarFunc { ScalarFunc::Upper => "upper".to_string(), ScalarFunc::Lower => "lower".to_string(), ScalarFunc::Random => "random".to_string(), + ScalarFunc::RandomBlob => "randomblob".to_string(), ScalarFunc::Trim => "trim".to_string(), ScalarFunc::LTrim => "ltrim".to_string(), ScalarFunc::RTrim => "rtrim".to_string(), @@ -169,6 +171,7 @@ impl Func { "upper" => Ok(Func::Scalar(ScalarFunc::Upper)), "lower" => Ok(Func::Scalar(ScalarFunc::Lower)), "random" => Ok(Func::Scalar(ScalarFunc::Random)), + "randomblob" => Ok(Func::Scalar(ScalarFunc::RandomBlob)), "trim" => Ok(Func::Scalar(ScalarFunc::Trim)), "ltrim" => Ok(Func::Scalar(ScalarFunc::LTrim)), "rtrim" => Ok(Func::Scalar(ScalarFunc::RTrim)), diff --git a/core/translate/expr.rs b/core/translate/expr.rs index c50cf14f3..e1a5adfd3 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1006,6 +1006,7 @@ pub fn translate_expr( | ScalarFunc::Typeof | ScalarFunc::Unicode | ScalarFunc::Quote + | ScalarFunc::RandomBlob | ScalarFunc::Sign | ScalarFunc::ZeroBlob => { let args = if let Some(args) = args { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 70d4a5526..fcd1d2690 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1531,6 +1531,7 @@ impl Program { | ScalarFunc::Typeof | ScalarFunc::Unicode | ScalarFunc::Quote + | ScalarFunc::RandomBlob | ScalarFunc::Sign | ScalarFunc::ZeroBlob => { let reg_value = state.registers[*start_reg].borrow_mut(); @@ -1543,6 +1544,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::RandomBlob => Some(exec_randomblob(reg_value)), ScalarFunc::ZeroBlob => Some(exec_zeroblob(reg_value)), _ => unreachable!(), }; @@ -2007,6 +2009,20 @@ fn exec_random() -> OwnedValue { OwnedValue::Integer(random_number) } +fn exec_randomblob(reg: &OwnedValue) -> OwnedValue { + let length = match reg { + OwnedValue::Integer(i) => *i, + OwnedValue::Float(f) => *f as i64, + OwnedValue::Text(t) => t.parse().unwrap_or(1), + _ => 1, + } + .max(1) as usize; + + let mut blob: Vec = vec![0; length]; + getrandom::getrandom(&mut blob).expect("Failed to generate random blob"); + OwnedValue::Blob(Rc::new(blob)) +} + fn exec_quote(value: &OwnedValue) -> OwnedValue { match value { OwnedValue::Null => OwnedValue::Text(OwnedValue::Null.to_string().into()), @@ -2323,10 +2339,10 @@ 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_unhex, exec_unicode, exec_upper, - exec_zeroblob, execute_sqlite_version, get_new_rowid, AggContext, Cursor, CursorResult, - LimboError, OwnedRecord, OwnedValue, Result, + exec_max, exec_min, exec_nullif, exec_quote, exec_random, exec_randomblob, exec_round, + exec_rtrim, exec_sign, exec_substring, exec_trim, exec_typeof, exec_unhex, exec_unicode, + exec_upper, exec_zeroblob, execute_sqlite_version, get_new_rowid, AggContext, Cursor, + CursorResult, LimboError, OwnedRecord, OwnedValue, Result, }; use mockall::{mock, predicate}; use rand::{rngs::mock::StepRng, thread_rng}; @@ -2779,6 +2795,67 @@ mod tests { } } + #[test] + fn test_exec_randomblob() { + struct TestCase { + input: OwnedValue, + expected_len: usize, + } + + let test_cases = vec![ + TestCase { + input: OwnedValue::Integer(5), + expected_len: 5, + }, + TestCase { + input: OwnedValue::Integer(0), + expected_len: 1, + }, + TestCase { + input: OwnedValue::Integer(-1), + expected_len: 1, + }, + TestCase { + input: OwnedValue::Text(Rc::new(String::from(""))), + expected_len: 1, + }, + TestCase { + input: OwnedValue::Text(Rc::new(String::from("5"))), + expected_len: 5, + }, + TestCase { + input: OwnedValue::Text(Rc::new(String::from("0"))), + expected_len: 1, + }, + TestCase { + input: OwnedValue::Text(Rc::new(String::from("-1"))), + expected_len: 1, + }, + TestCase { + input: OwnedValue::Float(2.9), + expected_len: 2, + }, + TestCase { + input: OwnedValue::Float(-3.14), + expected_len: 1, + }, + TestCase { + input: OwnedValue::Null, + expected_len: 1, + }, + ]; + + for test_case in &test_cases { + let result = exec_randomblob(&test_case.input); + match result { + OwnedValue::Blob(blob) => { + assert_eq!(blob.len(), test_case.expected_len); + } + _ => panic!("exec_randomblob did not return a Blob variant"), + } + } + } + #[test] fn test_exec_round() { let input_val = OwnedValue::Float(123.456); diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index f057a3120..e2cbf489a 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -539,6 +539,22 @@ do_execsql_test sign-null { SELECT sign(NULL); } {} +do_execsql_test randomblob-int-2 { + SELECT length(randomblob(2)); +} {2} + +do_execsql_test randomblob-int-0 { + SELECT length(randomblob(0)); +} {1} + +do_execsql_test randomblob-int-negative { + SELECT length(randomblob(-2)); +} {1} + +do_execsql_test randomblob-str-2 { + SELECT length(randomblob('2')); +} {2} + do_execsql_test zeroblob-int-0 { SELECT zeroblob(0) = x''; } {1}