diff --git a/COMPAT.md b/COMPAT.md index 730458614..b3e7fe2bb 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -112,7 +112,7 @@ This document describes the SQLite compatibility status of Limbo: | trim(X) | Yes | | | trim(X,Y) | Yes | | | typeof(X) | Yes | | -| unhex(X) | No | | +| unhex(X) | Yes | | | unhex(X,Y) | No | | | unicode(X) | Yes | | | unlikely(X) | No | | diff --git a/core/function.rs b/core/function.rs index f09ab6ef5..a58296ea5 100644 --- a/core/function.rs +++ b/core/function.rs @@ -77,6 +77,7 @@ pub enum ScalarFunc { SqliteVersion, UnixEpoch, Hex, + Unhex, } 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::Unhex => "unhex".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)), + "unhex" => Ok(Func::Scalar(ScalarFunc::Unhex)), _ => Err(()), } } diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 2cb41e48a..46fe1c826 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1215,7 +1215,8 @@ pub fn translate_expr( ScalarFunc::Trim | ScalarFunc::LTrim | ScalarFunc::RTrim - | ScalarFunc::Round => { + | ScalarFunc::Round + | ScalarFunc::Unhex => { let args = if let Some(args) = args { if args.len() > 2 { crate::bail_parse_error!( diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index a09bcf7cc..b1552a22f 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1551,6 +1551,12 @@ impl Program { let result = exec_hex(reg_value); state.registers[*dest] = result; } + ScalarFunc::Unhex => { + let reg_value = state.registers[*start_reg].clone(); + let ignored_chars = state.registers.get(*start_reg + 1); + let result = exec_unhex(®_value, ignored_chars); + state.registers[*dest] = result; + } ScalarFunc::Random => { state.registers[*dest] = exec_random(); } @@ -2171,6 +2177,20 @@ fn exec_hex(reg: &OwnedValue) -> OwnedValue { } } +fn exec_unhex(reg: &OwnedValue, ignored_chars: Option<&OwnedValue>) -> OwnedValue { + if ignored_chars.is_some() { + unimplemented!("unhex(X,Y) is not implemented"); + } + + match reg { + OwnedValue::Null => OwnedValue::Null, + _ => match hex::decode(reg.to_string()) { + Ok(bytes) => OwnedValue::Blob(Rc::new(bytes)), + Err(_) => OwnedValue::Null, + }, + } +} + fn exec_unicode(reg: &OwnedValue) -> OwnedValue { match reg { OwnedValue::Text(_) @@ -2292,7 +2312,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_unhex, exec_unicode, exec_upper, execute_sqlite_version, get_new_rowid, AggContext, Cursor, CursorResult, LimboError, OwnedRecord, OwnedValue, Result, }; @@ -2644,6 +2664,33 @@ mod tests { assert_eq!(exec_hex(&input_float), expected_val); } + #[test] + fn test_unhex() { + let input = OwnedValue::Text(Rc::new(String::from("6F"))); + let expected = OwnedValue::Blob(Rc::new(vec![0x6f])); + assert_eq!(exec_unhex(&input, None), expected); + + let input = OwnedValue::Text(Rc::new(String::from("6f"))); + let expected = OwnedValue::Blob(Rc::new(vec![0x6f])); + assert_eq!(exec_unhex(&input, None), expected); + + let input = OwnedValue::Text(Rc::new(String::from("611"))); + let expected = OwnedValue::Null; + assert_eq!(exec_unhex(&input, None), expected); + + let input = OwnedValue::Text(Rc::new(String::from(""))); + let expected = OwnedValue::Blob(Rc::new(vec![])); + assert_eq!(exec_unhex(&input, None), expected); + + let input = OwnedValue::Text(Rc::new(String::from("61x"))); + let expected = OwnedValue::Null; + assert_eq!(exec_unhex(&input, None), expected); + + let input = OwnedValue::Null; + let expected = OwnedValue::Null; + assert_eq!(exec_unhex(&input, None), expected); + } + #[test] fn test_abs() { let int_positive_reg = OwnedValue::Integer(10); diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index 28d8e9200..733b2f5c3 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -119,6 +119,30 @@ do_execsql_test hex-null { select hex(null) } {} +do_execsql_test unhex-str-ab { + SELECT unhex('6162'); +} {ab} + +do_execsql_test unhex-int-ab { + SELECT unhex(6162); +} {ab} + +do_execsql_test unhex-dot-uppercase { + SELECT unhex('2E'); +} {.} + +do_execsql_test unhex-dot-lowercase { + SELECT unhex('2e'); +} {.} + +do_execsql_test unhex-no-hex { + SELECT unhex('x'); +} {} + +do_execsql_test unhex-null { + SELECT unhex(NULL); +} {} + do_execsql_test trim { SELECT trim(' Limbo '); } {Limbo}