Merge 'Add randomblob(N) scalar function' from Lauri Virtanen

Relates to issue #144

Closes #355
This commit is contained in:
Pekka Enberg
2024-10-03 10:35:23 +03:00
5 changed files with 102 additions and 5 deletions

View File

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

View File

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

View File

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

View File

@@ -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<u8> = 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);

View File

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