From 5c00c576a5e014f45edbcaa5c51cdf4f3684c6f0 Mon Sep 17 00:00:00 2001 From: baishen Date: Sun, 22 Sep 2024 13:55:04 +0800 Subject: [PATCH] Add support for hex scalar function --- COMPAT.md | 2 +- Cargo.lock | 7 +++++++ core/Cargo.toml | 1 + core/function.rs | 3 +++ core/translate/expr.rs | 28 ++++++++++++++++++++++++++++ core/vdbe/mod.rs | 35 ++++++++++++++++++++++++++++++++++- testing/scalar-functions.test | 12 ++++++++++++ 7 files changed, 86 insertions(+), 2 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index abfae6006..eb702c402 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -69,7 +69,7 @@ This document describes the SQLite compatibility status of Limbo: | concat_ws(SEP,X,...) | Yes | | | format(FORMAT,...) | No | | | glob(X,Y) | Yes | | -| hex(X) | No | | +| hex(X) | Yes | | | ifnull(X,Y) | Yes | | | iif(X,Y,Z) | No | | | instr(X,Y) | No | | diff --git a/Cargo.lock b/Cargo.lock index bd7c50e55..94141a4ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -893,6 +893,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "home" version = "0.5.9" @@ -1117,6 +1123,7 @@ dependencies = [ "criterion", "fallible-iterator 0.3.0", "getrandom", + "hex", "indexmap", "io-uring", "jsonb", diff --git a/core/Cargo.toml b/core/Cargo.toml index 2fd236807..77405c16d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -30,6 +30,7 @@ mimalloc = { version = "*", default-features = false } [dependencies] cfg_block = "0.1.1" fallible-iterator = "0.3.0" +hex = "0.4.3" libc = "0.2.155" log = "0.4.20" nix = { version = "0.29.0", features = ["fs"] } diff --git a/core/function.rs b/core/function.rs index 74daaf941..f09ab6ef5 100644 --- a/core/function.rs +++ b/core/function.rs @@ -76,6 +76,7 @@ pub enum ScalarFunc { Quote, SqliteVersion, UnixEpoch, + Hex, } impl Display for ScalarFunc { @@ -110,6 +111,7 @@ impl Display for ScalarFunc { ScalarFunc::Quote => "quote".to_string(), ScalarFunc::SqliteVersion => "sqlite_version".to_string(), ScalarFunc::UnixEpoch => "unixepoch".to_string(), + ScalarFunc::Hex => "hex".to_string(), }; write!(f, "{}", str) } @@ -179,6 +181,7 @@ impl Func { "sqlite_version" => Ok(Func::Scalar(ScalarFunc::SqliteVersion)), "json" => Ok(Func::Json(JsonFunc::Json)), "unixepoch" => Ok(Func::Scalar(ScalarFunc::UnixEpoch)), + "hex" => Ok(Func::Scalar(ScalarFunc::Hex)), _ => Err(()), } } diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 724cedb83..37ff132ef 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1133,6 +1133,34 @@ pub fn translate_expr( }); Ok(target_register) } + ScalarFunc::Hex => { + let args = if let Some(args) = args { + if args.len() != 1 { + crate::bail_parse_error!( + "hex function must have exactly 1 argument", + ); + } + args + } else { + crate::bail_parse_error!("hex function with no arguments",); + }; + let regs = program.alloc_register(); + translate_expr( + program, + referenced_tables, + &args[0], + regs, + cursor_hint, + cached_results, + )?; + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: regs, + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } ScalarFunc::UnixEpoch => { let mut start_reg = 0; match args { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index b50c30b7b..14d355293 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1536,6 +1536,11 @@ impl Program { }; state.registers[*dest] = result.unwrap_or(OwnedValue::Null); } + ScalarFunc::Hex => { + let reg_value = state.registers[*start_reg].borrow_mut(); + let result = exec_hex(reg_value); + state.registers[*dest] = result; + } ScalarFunc::Random => { state.registers[*dest] = exec_random(); } @@ -2146,6 +2151,19 @@ fn exec_typeof(reg: &OwnedValue) -> OwnedValue { } } +fn exec_hex(reg: &OwnedValue) -> OwnedValue { + match reg { + OwnedValue::Text(_) + | OwnedValue::Integer(_) + | OwnedValue::Float(_) + | OwnedValue::Blob(_) => { + let text = reg.to_string(); + OwnedValue::Text(Rc::new(hex::encode_upper(text))) + } + _ => OwnedValue::Null, + } +} + fn exec_unicode(reg: &OwnedValue) -> OwnedValue { match reg { OwnedValue::Text(_) @@ -2265,7 +2283,7 @@ fn execute_sqlite_version(version_integer: i64) -> String { mod tests { use super::{ - exec_abs, exec_char, exec_if, exec_length, exec_like, exec_lower, exec_ltrim, exec_minmax, + exec_abs, exec_char, exec_hex, 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_typeof, exec_unicode, exec_upper, execute_sqlite_version, get_new_rowid, AggContext, Cursor, CursorResult, LimboError, OwnedRecord, OwnedValue, Result, @@ -2610,6 +2628,21 @@ mod tests { assert_eq!(exec_lower(&OwnedValue::Null).unwrap(), OwnedValue::Null) } + #[test] + fn test_hex() { + let input_str = OwnedValue::Text(Rc::new("limbo".to_string())); + let expected_val = OwnedValue::Text(Rc::new(String::from("6C696D626F"))); + assert_eq!(exec_hex(&input_str), expected_val); + + let input_int = OwnedValue::Integer(100); + let expected_val = OwnedValue::Text(Rc::new(String::from("313030"))); + assert_eq!(exec_hex(&input_int), expected_val); + + let input_float = OwnedValue::Float(12.34); + let expected_val = OwnedValue::Text(Rc::new(String::from("31322E3334"))); + assert_eq!(exec_hex(&input_float), expected_val); + } + #[test] fn test_abs() { let int_positive_reg = OwnedValue::Integer(10); diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index d1eabf2ec..f9f15e8c3 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -107,6 +107,18 @@ do_execsql_test lower-null { select lower(null) } {} +do_execsql_test hex { + select hex('limbo') +} {6C696D626F} + +do_execsql_test hex-number { + select hex(100) +} {313030} + +do_execsql_test hex-null { + select hex(null) +} {} + do_execsql_test trim { SELECT trim(' Limbo '); } {Limbo}