From 2fcae80902bfe9af004548311e690702b7b21c6f Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Fri, 20 Dec 2024 15:17:50 -0500 Subject: [PATCH] Create ext directory for outside funcs, add uuid to ext dir --- core/Cargo.toml | 1 + core/ext/mod.rs | 30 ++++ core/ext/uuid.rs | 334 +++++++++++++++++++++++++++++++++++++++ core/function.rs | 31 +--- core/lib.rs | 1 + core/translate/expr.rs | 127 ++++++++++----- core/vdbe/mod.rs | 347 +++++------------------------------------ 7 files changed, 501 insertions(+), 370 deletions(-) create mode 100644 core/ext/mod.rs create mode 100644 core/ext/uuid.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index 9051508df..25a0c6c90 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -22,6 +22,7 @@ json = [ "dep:pest", "dep:pest_derive", ] +uuid = ["dep:uuid"] [target.'cfg(target_os = "linux")'.dependencies] io-uring = "0.6.1" diff --git a/core/ext/mod.rs b/core/ext/mod.rs new file mode 100644 index 000000000..fea543869 --- /dev/null +++ b/core/ext/mod.rs @@ -0,0 +1,30 @@ +#[cfg(feature = "uuid")] +mod uuid; +#[cfg(feature = "uuid")] +pub use uuid::{exec_ts_from_uuid7, exec_uuid, exec_uuidblob, exec_uuidstr, UuidFunc}; + +#[derive(Debug, Clone, PartialEq)] +pub enum ExtFunc { + #[cfg(feature = "uuid")] + Uuid(UuidFunc), +} + +impl std::fmt::Display for ExtFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(feature = "uuid")] + ExtFunc::Uuid(uuidfn) => write!(f, "{}", uuidfn), + _ => write!(f, "unknown"), + } + } +} + +impl ExtFunc { + pub fn resolve_function(name: &str, num_args: usize) -> Result { + match name { + #[cfg(feature = "uuid")] + name => UuidFunc::resolve_function(name, num_args), + _ => Err(()), + } + } +} diff --git a/core/ext/uuid.rs b/core/ext/uuid.rs new file mode 100644 index 000000000..aa717c13d --- /dev/null +++ b/core/ext/uuid.rs @@ -0,0 +1,334 @@ +use super::ExtFunc; +use crate::{types::OwnedValue, LimboError}; +use std::rc::Rc; +use uuid::{ContextV7, Timestamp, Uuid}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum UuidFunc { + Uuid4Str, + Uuid4, + Uuid7, + Uuid7TS, + UuidStr, + UuidBlob, +} + +impl UuidFunc { + pub fn resolve_function(name: &str, num_args: usize) -> Result { + match name { + "uuid4_str" => Ok(ExtFunc::Uuid(UuidFunc::Uuid4Str)), + "uuid4" => Ok(ExtFunc::Uuid(UuidFunc::Uuid4)), + "uuid7" if num_args < 2 => Ok(ExtFunc::Uuid(UuidFunc::Uuid7)), + "uuid_str" if num_args == 1 => Ok(ExtFunc::Uuid(UuidFunc::UuidStr)), + "uuid_blob" if num_args == 1 => Ok(ExtFunc::Uuid(UuidFunc::UuidBlob)), + "uuid7_timestamp_ms" if num_args == 1 => Ok(ExtFunc::Uuid(UuidFunc::Uuid7TS)), + // postgres_compatability + "gen_random_uuid" => Ok(ExtFunc::Uuid(UuidFunc::Uuid4Str)), + _ => Err(()), + } + } +} + +impl std::fmt::Display for UuidFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UuidFunc::Uuid4Str => write!(f, "uuid4_str"), + UuidFunc::Uuid4 => write!(f, "uuid4"), + UuidFunc::Uuid7 => write!(f, "uuid7"), + UuidFunc::Uuid7TS => write!(f, "uuid7_timestamp_ms"), + UuidFunc::UuidStr => write!(f, "uuid_str"), + UuidFunc::UuidBlob => write!(f, "uuid_blob"), + } + } +} + +pub fn exec_uuid(var: &UuidFunc, sec: Option<&OwnedValue>) -> crate::Result { + match var { + UuidFunc::Uuid4 => Ok(OwnedValue::Blob(Rc::new( + Uuid::new_v4().into_bytes().to_vec(), + ))), + UuidFunc::Uuid4Str => Ok(OwnedValue::Text(Rc::new(Uuid::new_v4().to_string()))), + UuidFunc::Uuid7 => { + let uuid = match sec { + Some(OwnedValue::Integer(ref seconds)) => { + let ctx = ContextV7::new(); + if *seconds < 0 { + // not valid unix timestamp, error or null? + return Ok(OwnedValue::Null); + } + Uuid::new_v7(Timestamp::from_unix(ctx, *seconds as u64, 0)) + } + _ => Uuid::now_v7(), + }; + Ok(OwnedValue::Blob(Rc::new(uuid.into_bytes().to_vec()))) + } + _ => unreachable!(), + } +} + +pub fn exec_uuidstr(reg: &OwnedValue) -> crate::Result { + match reg { + OwnedValue::Blob(blob) => { + let uuid = Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Text(Rc::new(uuid.to_string()))) + } + OwnedValue::Text(val) => { + let uuid = Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Text(Rc::new(uuid.to_string()))) + } + OwnedValue::Null => Ok(OwnedValue::Null), + _ => Err(LimboError::ParseError( + "Invalid argument type for UUID function".to_string(), + )), + } +} + +pub fn exec_uuidblob(reg: &OwnedValue) -> crate::Result { + match reg { + OwnedValue::Text(val) => { + let uuid = Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Blob(Rc::new(uuid.as_bytes().to_vec()))) + } + OwnedValue::Blob(blob) => { + let uuid = Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string()))?; + Ok(OwnedValue::Blob(Rc::new(uuid.as_bytes().to_vec()))) + } + OwnedValue::Null => Ok(OwnedValue::Null), + _ => Err(LimboError::ParseError( + "Invalid argument type for UUID function".to_string(), + )), + } +} + +pub fn exec_ts_from_uuid7(reg: &OwnedValue) -> OwnedValue { + let uuid = match reg { + OwnedValue::Blob(blob) => { + Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string())) + } + OwnedValue::Text(val) => { + Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string())) + } + _ => Err(LimboError::ParseError( + "Invalid argument type for UUID function".to_string(), + )), + }; + match uuid { + Ok(uuid) => OwnedValue::Integer(uuid_to_unix(uuid.as_bytes()) as i64), + // display error? sqlean seems to set value to null + Err(_) => OwnedValue::Null, + } +} + +#[inline(always)] +fn uuid_to_unix(uuid: &[u8; 16]) -> u64 { + ((uuid[0] as u64) << 40) + | ((uuid[1] as u64) << 32) + | ((uuid[2] as u64) << 24) + | ((uuid[3] as u64) << 16) + | ((uuid[4] as u64) << 8) + | (uuid[5] as u64) +} + +#[cfg(test)] +#[cfg(feature = "uuid")] +pub mod test { + use super::UuidFunc; + use crate::types::OwnedValue; + #[test] + fn test_exec_uuid_v4blob() { + use super::exec_uuid; + use uuid::Uuid; + let func = UuidFunc::Uuid4; + let owned_val = exec_uuid(&func, None); + match owned_val { + Ok(OwnedValue::Blob(blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(&blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 4); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v4str() { + use super::{exec_uuid, UuidFunc}; + use uuid::Uuid; + let func = UuidFunc::Uuid4Str; + let owned_val = exec_uuid(&func, None); + match owned_val { + Ok(OwnedValue::Text(v4str)) => { + assert_eq!(v4str.len(), 36); + let uuid = Uuid::parse_str(&v4str); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 4); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v7_now() { + use super::{exec_uuid, UuidFunc}; + use uuid::Uuid; + let func = UuidFunc::Uuid7; + let owned_val = exec_uuid(&func, None); + match owned_val { + Ok(OwnedValue::Blob(blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(&blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v7_with_input() { + use super::{exec_uuid, UuidFunc}; + use uuid::Uuid; + let func = UuidFunc::Uuid7; + let owned_val = exec_uuid(&func, Some(&OwnedValue::Integer(946702800))); + match owned_val { + Ok(OwnedValue::Blob(blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(&blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v7_now_to_timestamp() { + use super::{exec_ts_from_uuid7, exec_uuid, UuidFunc}; + use uuid::Uuid; + let func = UuidFunc::Uuid7; + let owned_val = exec_uuid(&func, None); + match owned_val { + Ok(OwnedValue::Blob(ref blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + let result = exec_ts_from_uuid7(&owned_val.expect("uuid7")); + if let OwnedValue::Integer(ref ts) = result { + let unixnow = (std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() + * 1000) as i64; + assert!(*ts >= unixnow - 1000); + } + } + + #[test] + fn test_exec_uuid_v7_to_timestamp() { + use super::{exec_ts_from_uuid7, exec_uuid, UuidFunc}; + use uuid::Uuid; + let func = UuidFunc::Uuid7; + let owned_val = exec_uuid(&func, Some(&OwnedValue::Integer(946702800))); + match owned_val { + Ok(OwnedValue::Blob(ref blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + let result = exec_ts_from_uuid7(&owned_val.expect("uuid7")); + assert_eq!(result, OwnedValue::Integer(946702800 * 1000)); + if let OwnedValue::Integer(ts) = result { + let time = chrono::DateTime::from_timestamp(ts / 1000, 0); + assert_eq!( + time.unwrap(), + "2000-01-01T05:00:00Z" + .parse::>() + .unwrap() + ); + } + } + + #[test] + fn test_exec_uuid_v4_str_to_blob() { + use super::{exec_uuid, exec_uuidblob, UuidFunc}; + use uuid::Uuid; + let owned_val = exec_uuidblob( + &exec_uuid(&UuidFunc::Uuid4Str, None).expect("uuid v4 string to generate"), + ); + match owned_val { + Ok(OwnedValue::Blob(blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(&blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 4); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v7_str_to_blob() { + use super::{exec_uuid, exec_uuidblob, exec_uuidstr, UuidFunc}; + use uuid::Uuid; + // convert a v7 blob to a string then back to a blob + let owned_val = exec_uuidblob( + &exec_uuidstr(&exec_uuid(&UuidFunc::Uuid7, None).expect("uuid v7 blob to generate")) + .expect("uuid v7 string to generate"), + ); + match owned_val { + Ok(OwnedValue::Blob(blob)) => { + assert_eq!(blob.len(), 16); + let uuid = Uuid::from_slice(&blob); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v4_blob_to_str() { + use super::{exec_uuid, exec_uuidstr, UuidFunc}; + use uuid::Uuid; + // convert a v4 blob to a string + let owned_val = + exec_uuidstr(&exec_uuid(&UuidFunc::Uuid4, None).expect("uuid v7 blob to generate")); + match owned_val { + Ok(OwnedValue::Text(v4str)) => { + assert_eq!(v4str.len(), 36); + let uuid = Uuid::parse_str(&v4str); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 4); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } + + #[test] + fn test_exec_uuid_v7_blob_to_str() { + use super::{exec_uuid, exec_uuidstr}; + use uuid::Uuid; + // convert a v7 blob to a string + let owned_val = exec_uuidstr( + &exec_uuid(&UuidFunc::Uuid7, Some(&OwnedValue::Integer(123456789))) + .expect("uuid v7 blob to generate"), + ); + match owned_val { + Ok(OwnedValue::Text(v7str)) => { + assert_eq!(v7str.len(), 36); + let uuid = Uuid::parse_str(&v7str); + assert!(uuid.is_ok()); + assert_eq!(uuid.unwrap().get_version_num(), 7); + } + _ => panic!("exec_uuid did not return a Blob variant"), + } + } +} diff --git a/core/function.rs b/core/function.rs index b17d388bb..7d97432ca 100644 --- a/core/function.rs +++ b/core/function.rs @@ -1,6 +1,6 @@ +use crate::ext::ExtFunc; use std::fmt; use std::fmt::Display; - #[cfg(feature = "json")] #[derive(Debug, Clone, PartialEq)] pub enum JsonFunc { @@ -91,12 +91,6 @@ pub enum ScalarFunc { ZeroBlob, LastInsertRowid, Replace, - Uuid4, - Uuid4Str, - UuidStr, - UuidBlob, - Uuid7, - Uuid7TS, } impl Display for ScalarFunc { @@ -142,12 +136,6 @@ impl Display for ScalarFunc { ScalarFunc::ZeroBlob => "zeroblob".to_string(), ScalarFunc::LastInsertRowid => "last_insert_rowid".to_string(), ScalarFunc::Replace => "replace".to_string(), - ScalarFunc::Uuid4 => "uuid4".to_string(), - ScalarFunc::UuidStr => "uuid_str".to_string(), - ScalarFunc::UuidBlob => "uuid_blob".to_string(), - ScalarFunc::Uuid7 => "uuid7".to_string(), - ScalarFunc::Uuid4Str => "uuid4_str".to_string(), - ScalarFunc::Uuid7TS => "uuid7_timestamp_ms".to_string(), }; write!(f, "{}", str) } @@ -268,13 +256,14 @@ impl Display for MathFunc { } } -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] pub enum Func { Agg(AggFunc), Scalar(ScalarFunc), Math(MathFunc), #[cfg(feature = "json")] Json(JsonFunc), + Extention(ExtFunc), } impl Display for Func { @@ -285,6 +274,7 @@ impl Display for Func { Func::Math(math_func) => write!(f, "{}", math_func), #[cfg(feature = "json")] Func::Json(json_func) => write!(f, "{}", json_func), + Func::Extention(ext_func) => write!(f, "{}", ext_func), } } } @@ -337,14 +327,6 @@ impl Func { "typeof" => Ok(Func::Scalar(ScalarFunc::Typeof)), "last_insert_rowid" => Ok(Func::Scalar(ScalarFunc::LastInsertRowid)), "unicode" => Ok(Func::Scalar(ScalarFunc::Unicode)), - "uuid4_str" => Ok(Func::Scalar(ScalarFunc::Uuid4Str)), - "uuid4" => Ok(Func::Scalar(ScalarFunc::Uuid4)), - "uuid7" => Ok(Func::Scalar(ScalarFunc::Uuid7)), - "uuid_str" => Ok(Func::Scalar(ScalarFunc::UuidStr)), - "uuid_blob" => Ok(Func::Scalar(ScalarFunc::UuidBlob)), - "uuid7_timestamp_ms" => Ok(Func::Scalar(ScalarFunc::Uuid7TS)), - // postgres_compatability - "gen_random_uuid" => Ok(Func::Scalar(ScalarFunc::Uuid4Str)), "quote" => Ok(Func::Scalar(ScalarFunc::Quote)), "sqlite_version" => Ok(Func::Scalar(ScalarFunc::SqliteVersion)), "replace" => Ok(Func::Scalar(ScalarFunc::Replace)), @@ -386,7 +368,10 @@ impl Func { "tan" => Ok(Func::Math(MathFunc::Tan)), "tanh" => Ok(Func::Math(MathFunc::Tanh)), "trunc" => Ok(Func::Math(MathFunc::Trunc)), - _ => Err(()), + _ => match ExtFunc::resolve_function(name, arg_count) { + Ok(ext_func) => Ok(Func::Extention(ext_func)), + Err(_) => Err(()), + }, } } } diff --git a/core/lib.rs b/core/lib.rs index 1f5668d76..79e06abfb 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -1,4 +1,5 @@ mod error; +mod ext; mod function; mod io; #[cfg(feature = "json")] diff --git a/core/translate/expr.rs b/core/translate/expr.rs index ea81d9ead..523be6e51 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1,5 +1,7 @@ use sqlite3_parser::ast::{self, UnaryOperator}; +#[cfg(feature = "uuid")] +use crate::ext::{ExtFunc, UuidFunc}; #[cfg(feature = "json")] use crate::function::JsonFunc; use crate::function::{AggFunc, Func, FuncCtx, MathFuncArity, ScalarFunc}; @@ -1194,10 +1196,7 @@ pub fn translate_expr( | ScalarFunc::RandomBlob | ScalarFunc::Sign | ScalarFunc::Soundex - | ScalarFunc::ZeroBlob - | ScalarFunc::UuidStr - | ScalarFunc::UuidBlob - | ScalarFunc::Uuid7TS => { + | ScalarFunc::ZeroBlob => { let args = if let Some(args) = args { if args.len() != 1 { crate::bail_parse_error!( @@ -1229,39 +1228,7 @@ pub fn translate_expr( }); Ok(target_register) } - ScalarFunc::Uuid7 => { - let args = match args { - Some(args) if args.len() > 1 => crate::bail_parse_error!( - "{} function with more than 1 argument", - srf.to_string() - ), - Some(args) => args, - None => &vec![], - }; - let mut start_reg = None; - if let Some(arg) = args.first() { - let reg = program.alloc_register(); - start_reg = Some(reg); - translate_expr( - program, - referenced_tables, - arg, - reg, - precomputed_exprs_to_registers, - )?; - if let ast::Expr::Literal(_) = arg { - program.mark_last_insn_constant() - } - } - program.emit_insn(Insn::Function { - constant_mask: 0, - start_reg: start_reg.unwrap_or(target_register), - dest: target_register, - func: func_ctx, - }); - Ok(target_register) - } - ScalarFunc::Random | ScalarFunc::Uuid4 | ScalarFunc::Uuid4Str => { + ScalarFunc::Random => { if args.is_some() { crate::bail_parse_error!( "{} function with arguments", @@ -1648,6 +1615,92 @@ pub fn translate_expr( } } } + Func::Extention(ext_func) => match ext_func { + #[cfg(feature = "uuid")] + ExtFunc::Uuid(ref uuid_fn) => match uuid_fn { + UuidFunc::UuidStr | UuidFunc::UuidBlob | UuidFunc::Uuid7TS => { + let args = if let Some(args) = args { + if args.len() != 1 { + crate::bail_parse_error!( + "{} function with not exactly 1 argument", + ext_func.to_string() + ); + } + args + } else { + crate::bail_parse_error!( + "{} function with no arguments", + ext_func.to_string() + ); + }; + + let regs = program.alloc_register(); + translate_expr( + program, + referenced_tables, + &args[0], + regs, + precomputed_exprs_to_registers, + )?; + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: regs, + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } + UuidFunc::Uuid4 | UuidFunc::Uuid4Str => { + if args.is_some() { + crate::bail_parse_error!( + "{} function with arguments", + ext_func.to_string() + ); + } + let regs = program.alloc_register(); + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: regs, + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } + UuidFunc::Uuid7 => { + let args = match args { + Some(args) if args.len() > 1 => crate::bail_parse_error!( + "{} function with more than 1 argument", + ext_func.to_string() + ), + Some(args) => args, + None => &vec![], + }; + let mut start_reg = None; + if let Some(arg) = args.first() { + let reg = program.alloc_register(); + start_reg = Some(reg); + translate_expr( + program, + referenced_tables, + arg, + reg, + precomputed_exprs_to_registers, + )?; + if let ast::Expr::Literal(_) = arg { + program.mark_last_insn_constant() + } + } + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: start_reg.unwrap_or(target_register), + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } + }, + _ => unreachable!("{ext_func} not implemented yet"), + }, Func::Math(math_func) => match math_func.arity() { MathFuncArity::Nullary => { if args.is_some() { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 68cfa264f..b5eb9d60f 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -24,7 +24,9 @@ pub mod sorter; mod datetime; use crate::error::{LimboError, SQLITE_CONSTRAINT_PRIMARYKEY}; -use crate::function::{AggFunc, FuncCtx, MathFunc, MathFuncArity, ScalarFunc}; +#[cfg(feature = "uuid")] +use crate::ext::{exec_ts_from_uuid7, exec_uuid, exec_uuidblob, exec_uuidstr, ExtFunc, UuidFunc}; +use crate::function::{AggFunc, Func, FuncCtx, MathFunc, MathFuncArity, ScalarFunc}; use crate::pseudo::PseudoCursor; use crate::schema::Table; use crate::storage::sqlite3_ondisk::DatabaseHeader; @@ -46,10 +48,10 @@ use regex::Regex; use std::borrow::{Borrow, BorrowMut}; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; -use std::fmt::Display; use std::rc::{Rc, Weak}; use uuid::{ContextV7, Timestamp, Uuid}; + pub type BranchOffset = i64; use macros::Description; pub type CursorID = usize; @@ -2397,10 +2399,7 @@ impl Program { | ScalarFunc::RandomBlob | ScalarFunc::Sign | ScalarFunc::Soundex - | ScalarFunc::ZeroBlob - | ScalarFunc::UuidStr - | ScalarFunc::UuidBlob - | ScalarFunc::Uuid7TS => { + | ScalarFunc::ZeroBlob => { let reg_value = state.registers[*start_reg].borrow_mut(); let result = match scalar_func { ScalarFunc::Sign => exec_sign(reg_value), @@ -2415,26 +2414,10 @@ impl Program { ScalarFunc::RandomBlob => Some(exec_randomblob(reg_value)), ScalarFunc::ZeroBlob => Some(exec_zeroblob(reg_value)), ScalarFunc::Soundex => Some(exec_soundex(reg_value)), - ScalarFunc::UuidStr => Some(exec_uuidstr(reg_value)?), - ScalarFunc::UuidBlob => Some(exec_uuidblob(reg_value)?), - ScalarFunc::Uuid7TS => Some(exec_ts_from_uuid7(reg_value)), _ => unreachable!(), }; state.registers[*dest] = result.unwrap_or(OwnedValue::Null); } - ScalarFunc::Uuid7 => match arg_count { - 0 => { - state.registers[*dest] = - exec_uuid(scalar_func, None).unwrap_or(OwnedValue::Null); - } - 1 => { - let reg_value = state.registers[*start_reg].borrow(); - state.registers[*dest] = - exec_uuid(scalar_func, Some(reg_value)) - .unwrap_or(OwnedValue::Null); - } - _ => unreachable!(), - }, ScalarFunc::Hex => { let reg_value = state.registers[*start_reg].borrow_mut(); let result = exec_hex(reg_value); @@ -2449,9 +2432,6 @@ impl Program { ScalarFunc::Random => { state.registers[*dest] = exec_random(); } - ScalarFunc::Uuid4 | ScalarFunc::Uuid4Str => { - state.registers[*dest] = exec_uuid(scalar_func, None)?; - } ScalarFunc::Trim => { let reg_value = state.registers[*start_reg].clone(); let pattern_value = state.registers.get(*start_reg + 1).cloned(); @@ -2555,6 +2535,38 @@ impl Program { state.registers[*dest] = exec_replace(source, pattern, replacement); } }, + Func::Extention(extfn) => match extfn { + #[cfg(feature = "uuid")] + ExtFunc::Uuid(uuidfn) => match uuidfn { + UuidFunc::Uuid4 | UuidFunc::Uuid4Str => { + state.registers[*dest] = exec_uuid(uuidfn, None)? + } + UuidFunc::Uuid7 => match arg_count { + 0 => { + state.registers[*dest] = + exec_uuid(uuidfn, None).unwrap_or(OwnedValue::Null); + } + 1 => { + let reg_value = state.registers[*start_reg].borrow(); + state.registers[*dest] = exec_uuid(uuidfn, Some(reg_value)) + .unwrap_or(OwnedValue::Null); + } + _ => unreachable!(), + }, + _ => { + // remaining accept 1 arg + let reg_value = state.registers[*start_reg].borrow(); + state.registers[*dest] = match uuidfn { + UuidFunc::Uuid7TS => Some(exec_ts_from_uuid7(reg_value)), + UuidFunc::UuidStr => exec_uuidstr(reg_value).ok(), + UuidFunc::UuidBlob => exec_uuidblob(reg_value).ok(), + _ => unreachable!(), + } + .unwrap_or(OwnedValue::Null); + } + }, + _ => unreachable!(), // when more extension types are added + }, crate::function::Func::Math(math_func) => match math_func.arity() { MathFuncArity::Nullary => match math_func { MathFunc::Pi => { @@ -3117,93 +3129,6 @@ fn exec_random() -> OwnedValue { OwnedValue::Integer(random_number) } -fn exec_uuid(var: &ScalarFunc, sec: Option<&OwnedValue>) -> Result { - match var { - ScalarFunc::Uuid4 => Ok(OwnedValue::Blob(Rc::new( - Uuid::new_v4().into_bytes().to_vec(), - ))), - ScalarFunc::Uuid4Str => Ok(OwnedValue::Text(Rc::new(Uuid::new_v4().to_string()))), - ScalarFunc::Uuid7 => { - let uuid = match sec { - Some(OwnedValue::Integer(ref seconds)) => { - let ctx = ContextV7::new(); - if *seconds < 0 { - // not valid unix timestamp, error or null? - return Ok(OwnedValue::Null); - } - Uuid::new_v7(Timestamp::from_unix(ctx, *seconds as u64, 0)) - } - _ => Uuid::now_v7(), - }; - Ok(OwnedValue::Blob(Rc::new(uuid.into_bytes().to_vec()))) - } - _ => unreachable!(), - } -} - -fn exec_uuidstr(reg: &OwnedValue) -> Result { - match reg { - OwnedValue::Blob(blob) => { - let uuid = Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string()))?; - Ok(OwnedValue::Text(Rc::new(uuid.to_string()))) - } - OwnedValue::Text(val) => { - let uuid = Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string()))?; - Ok(OwnedValue::Text(Rc::new(uuid.to_string()))) - } - OwnedValue::Null => Ok(OwnedValue::Null), - _ => Err(LimboError::ParseError( - "Invalid argument type for UUID function".to_string(), - )), - } -} - -fn exec_uuidblob(reg: &OwnedValue) -> Result { - match reg { - OwnedValue::Text(val) => { - let uuid = Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string()))?; - Ok(OwnedValue::Blob(Rc::new(uuid.as_bytes().to_vec()))) - } - OwnedValue::Blob(blob) => { - let uuid = Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string()))?; - Ok(OwnedValue::Blob(Rc::new(uuid.as_bytes().to_vec()))) - } - OwnedValue::Null => Ok(OwnedValue::Null), - _ => Err(LimboError::ParseError( - "Invalid argument type for UUID function".to_string(), - )), - } -} - -fn exec_ts_from_uuid7(reg: &OwnedValue) -> OwnedValue { - let uuid = match reg { - OwnedValue::Blob(blob) => { - Uuid::from_slice(blob).map_err(|e| LimboError::ParseError(e.to_string())) - } - OwnedValue::Text(val) => { - Uuid::parse_str(val).map_err(|e| LimboError::ParseError(e.to_string())) - } - _ => Err(LimboError::ParseError( - "Invalid argument type for UUID function".to_string(), - )), - }; - match uuid { - Ok(uuid) => OwnedValue::Integer(uuid_to_unix(uuid.as_bytes()) as i64), - // display error? sqlean seems to set value to null - Err(_) => OwnedValue::Null, - } -} - -#[inline(always)] -fn uuid_to_unix(uuid: &[u8; 16]) -> u64 { - ((uuid[0] as u64) << 40) - | ((uuid[1] as u64) << 32) - | ((uuid[2] as u64) << 24) - | ((uuid[3] as u64) << 16) - | ((uuid[4] as u64) << 8) - | (uuid[5] as u64) -} - fn exec_randomblob(reg: &OwnedValue) -> OwnedValue { let length = match reg { OwnedValue::Integer(i) => *i, @@ -4970,202 +4895,4 @@ mod tests { expected_str ); } - - #[test] - fn test_exec_uuid_v4blob() { - use super::{exec_uuid, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::Uuid4; - let owned_val = exec_uuid(&func, None); - match owned_val { - Ok(OwnedValue::Blob(blob)) => { - assert_eq!(blob.len(), 16); - let uuid = Uuid::from_slice(&blob); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 4); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - } - - #[test] - fn test_exec_uuid_v4str() { - use super::{exec_uuid, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::Uuid4Str; - let owned_val = exec_uuid(&func, None); - match owned_val { - Ok(OwnedValue::Text(v4str)) => { - assert_eq!(v4str.len(), 36); - let uuid = Uuid::parse_str(&v4str); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 4); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - } - - #[test] - fn test_exec_uuid_v7_now() { - use super::{exec_uuid, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::Uuid7; - let owned_val = exec_uuid(&func, None); - match owned_val { - Ok(OwnedValue::Blob(blob)) => { - assert_eq!(blob.len(), 16); - let uuid = Uuid::from_slice(&blob); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 7); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - } - - #[test] - fn test_exec_uuid_v7_with_input() { - use super::{exec_uuid, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::Uuid7; - let owned_val = exec_uuid(&func, Some(&OwnedValue::Integer(946702800))); - match owned_val { - Ok(OwnedValue::Blob(blob)) => { - assert_eq!(blob.len(), 16); - let uuid = Uuid::from_slice(&blob); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 7); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - } - - #[test] - fn test_exec_uuid_v7_now_to_timestamp() { - use super::{exec_ts_from_uuid7, exec_uuid, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::Uuid7; - let owned_val = exec_uuid(&func, None); - match owned_val { - Ok(OwnedValue::Blob(ref blob)) => { - assert_eq!(blob.len(), 16); - let uuid = Uuid::from_slice(blob); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 7); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - let result = exec_ts_from_uuid7(&owned_val.expect("uuid7")); - if let OwnedValue::Integer(ref ts) = result { - let unixnow = (std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() - * 1000) as i64; - assert!(*ts >= unixnow - 1000); - } - } - - #[test] - fn test_exec_uuid_v7_to_timestamp() { - use super::{exec_ts_from_uuid7, exec_uuid, ScalarFunc}; - use uuid::Uuid; - let func = ScalarFunc::Uuid7; - let owned_val = exec_uuid(&func, Some(&OwnedValue::Integer(946702800))); - match owned_val { - Ok(OwnedValue::Blob(ref blob)) => { - assert_eq!(blob.len(), 16); - let uuid = Uuid::from_slice(blob); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 7); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - let result = exec_ts_from_uuid7(&owned_val.expect("uuid7")); - assert_eq!(result, OwnedValue::Integer(946702800 * 1000)); - if let OwnedValue::Integer(ts) = result { - let time = chrono::DateTime::from_timestamp(ts / 1000, 0); - assert_eq!( - time.unwrap(), - "2000-01-01T05:00:00Z" - .parse::>() - .unwrap() - ); - } - } - - #[test] - fn test_exec_uuid_v4_str_to_blob() { - use super::{exec_uuid, exec_uuidblob, ScalarFunc}; - use uuid::Uuid; - let owned_val = exec_uuidblob( - &exec_uuid(&ScalarFunc::Uuid4Str, None).expect("uuid v4 string to generate"), - ); - match owned_val { - Ok(OwnedValue::Blob(blob)) => { - assert_eq!(blob.len(), 16); - let uuid = Uuid::from_slice(&blob); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 4); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - } - - #[test] - fn test_exec_uuid_v7_str_to_blob() { - use super::{exec_uuid, exec_uuidblob, exec_uuidstr, ScalarFunc}; - use uuid::Uuid; - // convert a v7 blob to a string then back to a blob - let owned_val = exec_uuidblob( - &exec_uuidstr(&exec_uuid(&ScalarFunc::Uuid7, None).expect("uuid v7 blob to generate")) - .expect("uuid v7 string to generate"), - ); - match owned_val { - Ok(OwnedValue::Blob(blob)) => { - assert_eq!(blob.len(), 16); - let uuid = Uuid::from_slice(&blob); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 7); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - } - - #[test] - fn test_exec_uuid_v4_blob_to_str() { - use super::{exec_uuid, exec_uuidstr, ScalarFunc}; - use uuid::Uuid; - // convert a v4 blob to a string - let owned_val = - exec_uuidstr(&exec_uuid(&ScalarFunc::Uuid4, None).expect("uuid v7 blob to generate")); - match owned_val { - Ok(OwnedValue::Text(v4str)) => { - assert_eq!(v4str.len(), 36); - let uuid = Uuid::parse_str(&v4str); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 4); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - } - - #[test] - fn test_exec_uuid_v7_blob_to_str() { - use super::{exec_uuid, exec_uuidstr, ScalarFunc}; - use uuid::Uuid; - // convert a v7 blob to a string - let owned_val = exec_uuidstr( - &exec_uuid(&ScalarFunc::Uuid7, Some(&OwnedValue::Integer(123456789))) - .expect("uuid v7 blob to generate"), - ); - match owned_val { - Ok(OwnedValue::Text(v7str)) => { - assert_eq!(v7str.len(), 36); - let uuid = Uuid::parse_str(&v7str); - assert!(uuid.is_ok()); - assert_eq!(uuid.unwrap().get_version_num(), 7); - } - _ => panic!("exec_uuid did not return a Blob variant"), - } - } }