diff --git a/Cargo.lock b/Cargo.lock index 0e2deb1ea..6d23ae5f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1394,6 +1394,7 @@ name = "limbo_percentile" version = "0.0.13" dependencies = [ "limbo_ext", + "mimalloc", ] [[package]] @@ -1402,6 +1403,7 @@ version = "0.0.13" dependencies = [ "limbo_ext", "log", + "mimalloc", "regex", ] @@ -1437,7 +1439,7 @@ name = "limbo_uuid" version = "0.0.13" dependencies = [ "limbo_ext", - "log", + "mimalloc", "uuid", ] diff --git a/core/types.rs b/core/types.rs index 92e4714c7..98075d773 100644 --- a/core/types.rs +++ b/core/types.rs @@ -130,38 +130,41 @@ impl OwnedValue { } } - pub fn from_ffi(v: &ExtValue) -> Self { + pub fn from_ffi(v: &ExtValue) -> Result { match v.value_type() { - ExtValueType::Null => OwnedValue::Null, + ExtValueType::Null => Ok(OwnedValue::Null), ExtValueType::Integer => { let Some(int) = v.to_integer() else { - return OwnedValue::Null; + return Ok(OwnedValue::Null); }; - OwnedValue::Integer(int) + Ok(OwnedValue::Integer(int)) } ExtValueType::Float => { let Some(float) = v.to_float() else { - return OwnedValue::Null; + return Ok(OwnedValue::Null); }; - OwnedValue::Float(float) + Ok(OwnedValue::Float(float)) } ExtValueType::Text => { let Some(text) = v.to_text() else { - return OwnedValue::Null; + return Ok(OwnedValue::Null); }; - OwnedValue::build_text(Rc::new(text)) + Ok(OwnedValue::build_text(Rc::new(text.to_string()))) } ExtValueType::Blob => { let Some(blob) = v.to_blob() else { - return OwnedValue::Null; + return Ok(OwnedValue::Null); }; - OwnedValue::Blob(Rc::new(blob)) + Ok(OwnedValue::Blob(Rc::new(blob))) } ExtValueType::Error => { - let Some(err) = v.to_error() else { - return OwnedValue::Null; + let Some(err) = v.to_error_details() else { + return Ok(OwnedValue::Null); }; - OwnedValue::Text(LimboText::new(Rc::new(err))) + match err { + (_, Some(msg)) => Err(LimboError::ExtensionError(msg)), + (code, None) => Err(LimboError::ExtensionError(code.to_string())), + } } } } @@ -181,13 +184,15 @@ pub enum AggContext { const NULL: OwnedValue = OwnedValue::Null; impl AggContext { - pub fn compute_external(&mut self) { + pub fn compute_external(&mut self) -> Result<()> { if let Self::External(ext_state) = self { if ext_state.finalized_value.is_none() { let final_value = unsafe { (ext_state.finalize_fn)(ext_state.state) }; - ext_state.cache_final_value(OwnedValue::from_ffi(&final_value)); + ext_state.cache_final_value(OwnedValue::from_ffi(&final_value)?); + unsafe { final_value.free() }; } } + Ok(()) } pub fn final_value(&self) -> &OwnedValue { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 36cc8e4cb..3d6b790f2 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -163,8 +163,16 @@ macro_rules! call_external_function { ) => {{ if $arg_count == 0 { let result_c_value: ExtValue = unsafe { ($func_ptr)(0, std::ptr::null()) }; - let result_ov = OwnedValue::from_ffi(&result_c_value); - $state.registers[$dest_register] = result_ov; + match OwnedValue::from_ffi(&result_c_value) { + Ok(result_ov) => { + $state.registers[$dest_register] = result_ov; + unsafe { result_c_value.free() }; + } + Err(e) => { + unsafe { result_c_value.free() }; + return Err(e); + } + } } else { let register_slice = &$state.registers[$start_reg..$start_reg + $arg_count]; let mut ext_values: Vec = Vec::with_capacity($arg_count); @@ -174,8 +182,16 @@ macro_rules! call_external_function { } let argv_ptr = ext_values.as_ptr(); let result_c_value: ExtValue = unsafe { ($func_ptr)($arg_count as i32, argv_ptr) }; - let result_ov = OwnedValue::from_ffi(&result_c_value); - $state.registers[$dest_register] = result_ov; + match OwnedValue::from_ffi(&result_c_value) { + Ok(result_ov) => { + $state.registers[$dest_register] = result_ov; + unsafe { result_c_value.free() }; + } + Err(e) => { + unsafe { result_c_value.free() }; + return Err(e); + } + } } }}; } @@ -1553,7 +1569,7 @@ impl Program { AggFunc::Min => {} AggFunc::GroupConcat | AggFunc::StringAgg => {} AggFunc::External(_) => { - agg.compute_external(); + agg.compute_external()?; } }, OwnedValue::Null => { diff --git a/extensions/core/README.md b/extensions/core/README.md index 6e87743e0..bcb7ff86f 100644 --- a/extensions/core/README.md +++ b/extensions/core/README.md @@ -19,13 +19,18 @@ Add the crate to your `Cargo.toml`: ```toml [dependencies] limbo_ext = { path = "path/to/limbo/extensions/core" } # temporary until crate is published + +# mimalloc is required if you intend on linking dynamically. It is imported for you by the register_extension +# macro, so no configuration is needed. But it must be added to your Cargo.toml +[target.'cfg(not(target_family = "wasm"))'.dependencies] +mimalloc = { version = "*", default-features = false } ``` -**NOTE** Crate must be of type `cdylib` +**NOTE** Crate must be of type `cdylib` if you wish to link dynamically ``` [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "lib"] ``` `cargo build` will output a shared library that can be loaded with `.load target/debug/libyour_crate_name` diff --git a/extensions/core/src/types.rs b/extensions/core/src/types.rs index 9d69aa942..74fa670ad 100644 --- a/extensions/core/src/types.rs +++ b/extensions/core/src/types.rs @@ -1,7 +1,8 @@ -use std::{fmt::Display, os::raw::c_void}; +use std::fmt::Display; /// Error type is of type ExtError which can be /// either a user defined error or an error code +#[derive(Clone, Copy)] #[repr(C)] pub enum ResultCode { OK = 0, @@ -18,12 +19,17 @@ pub enum ResultCode { Unimplemented = 11, Internal = 12, Unavailable = 13, + CustomError = 14, } impl ResultCode { pub fn is_ok(&self) -> bool { matches!(self, ResultCode::OK) } + + pub fn has_error_set(&self) -> bool { + matches!(self, ResultCode::CustomError) + } } impl Display for ResultCode { @@ -31,7 +37,7 @@ impl Display for ResultCode { match self { ResultCode::OK => write!(f, "OK"), ResultCode::Error => write!(f, "Error"), - ResultCode::InvalidArgs => write!(f, "InvalidArgs"), + ResultCode::InvalidArgs => write!(f, "Invalid Argument"), ResultCode::Unknown => write!(f, "Unknown"), ResultCode::OoM => write!(f, "Out of Memory"), ResultCode::Corrupt => write!(f, "Corrupt"), @@ -43,6 +49,7 @@ impl Display for ResultCode { ResultCode::Unimplemented => write!(f, "Unimplemented"), ResultCode::Internal => write!(f, "Internal Error"), ResultCode::Unavailable => write!(f, "Unavailable"), + ResultCode::CustomError => write!(f, "Error "), } } } @@ -61,31 +68,35 @@ pub enum ValueType { #[repr(C)] pub struct Value { value_type: ValueType, - value: *mut c_void, + value: ValueData, +} + +#[repr(C)] +union ValueData { + int: i64, + float: f64, + text: *const TextValue, + blob: *const Blob, + error: *const ErrValue, } impl std::fmt::Debug for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.value.is_null() { - return write!(f, "{:?}: Null", self.value_type); - } match self.value_type { ValueType::Null => write!(f, "Value {{ Null }}"), - ValueType::Integer => write!(f, "Value {{ Integer: {} }}", unsafe { - *(self.value as *const i64) - }), - ValueType::Float => write!(f, "Value {{ Float: {} }}", unsafe { - *(self.value as *const f64) - }), - ValueType::Text => write!(f, "Value {{ Text: {:?} }}", unsafe { - &*(self.value as *const TextValue) - }), - ValueType::Blob => write!(f, "Value {{ Blob: {:?} }}", unsafe { - &*(self.value as *const Blob) - }), - ValueType::Error => write!(f, "Value {{ Error: {:?} }}", unsafe { - &*(self.value as *const TextValue) - }), + ValueType::Integer => write!( + f, + "Value {{ Integer: {} }}", + self.to_integer().unwrap_or_default() + ), + ValueType::Float => write!( + f, + "Value {{ Float: {} }}", + self.to_float().unwrap_or_default() + ), + ValueType::Text => write!(f, "Value {{ Text: {:?} }}", self.to_text()), + ValueType::Blob => write!(f, "Value {{ Blob: {:?} }}", self.to_blob()), + ValueType::Error => write!(f, "Value {{ Error }}"), } } } @@ -123,6 +134,17 @@ impl TextValue { } } + pub(crate) fn new_boxed(s: String) -> Box { + let buffer = s.into_boxed_str(); + let ptr = buffer.as_ptr(); + let len = buffer.len(); + std::mem::forget(buffer); + Box::new(Self { + text: ptr, + len: len as u32, + }) + } + fn as_str(&self) -> &str { if self.text.is_null() { return ""; @@ -133,6 +155,39 @@ impl TextValue { } } +#[repr(C)] +pub struct ErrValue { + code: ResultCode, + message: *mut TextValue, +} + +impl ErrValue { + fn new(code: ResultCode) -> Self { + Self { + code, + message: std::ptr::null_mut(), + } + } + + fn new_with_message(code: ResultCode, message: String) -> Self { + let buffer = message.into_boxed_str(); + let ptr = buffer.as_ptr(); + let len = buffer.len(); + std::mem::forget(buffer); + let text_value = TextValue::new(ptr, len); + Self { + code, + message: Box::into_raw(Box::new(text_value)), + } + } + + unsafe fn free(self) { + if !self.message.is_null() { + let _ = Box::from_raw(self.message); // Freed by the same library + } + } +} + #[repr(C)] pub struct Blob { data: *const u8, @@ -156,40 +211,42 @@ impl Value { pub fn null() -> Self { Self { value_type: ValueType::Null, - value: std::ptr::null_mut(), + value: ValueData { int: 0 }, } } /// Returns the value type of the Value + /// # Safety + /// This function accesses the value_type field of the union. + /// it is safe to call this function as long as the value was properly + /// constructed with one of the provided methods pub fn value_type(&self) -> ValueType { self.value_type } - /// Returns the float value if the Value is the proper type + /// Returns the float value or casts the relevant value to a float pub fn to_float(&self) -> Option { - if self.value.is_null() { - return None; - } match self.value_type { - ValueType::Float => Some(unsafe { *(self.value as *const f64) }), - ValueType::Integer => Some(unsafe { *(self.value as *const i64) as f64 }), + ValueType::Float => Some(unsafe { self.value.float }), + ValueType::Integer => Some(unsafe { self.value.int } as f64), ValueType::Text => { - let txt = unsafe { &*(self.value as *const TextValue) }; - txt.as_str().parse().ok() + let txt = self.to_text().unwrap_or_default(); + txt.parse().ok() } _ => None, } } + /// Returns the text value if the Value is the proper type - pub fn to_text(&self) -> Option { - if self.value_type != ValueType::Text { - return None; + pub fn to_text(&self) -> Option<&str> { + unsafe { + if self.value_type == ValueType::Text && !self.value.text.is_null() { + let txt = &*self.value.text; + Some(txt.as_str()) + } else { + None + } } - if self.value.is_null() { - return None; - } - let txt = unsafe { &*(self.value as *const TextValue) }; - Some(String::from(txt.as_str())) } /// Returns the blob value if the Value is the proper type @@ -197,119 +254,120 @@ impl Value { if self.value_type != ValueType::Blob { return None; } - if self.value.is_null() { + if unsafe { self.value.blob.is_null() } { return None; } - let blob = unsafe { &*(self.value as *const Blob) }; + let blob = unsafe { &*(self.value.blob) }; let slice = unsafe { std::slice::from_raw_parts(blob.data, blob.size as usize) }; Some(slice.to_vec()) } /// Returns the integer value if the Value is the proper type pub fn to_integer(&self) -> Option { - if self.value.is_null() { - return None; - } match self.value_type() { - ValueType::Integer => Some(unsafe { *(self.value as *const i64) }), - ValueType::Float => Some(unsafe { *(self.value as *const f64) } as i64), - ValueType::Text => { - let txt = unsafe { &*(self.value as *const TextValue) }; - txt.as_str().parse().ok() - } + ValueType::Integer => Some(unsafe { self.value.int }), + ValueType::Float => Some(unsafe { self.value.float } as i64), + ValueType::Text => self + .to_text() + .map(|txt| txt.parse::().unwrap_or_default()), _ => None, } } - /// Returns the error message if the value is an error - pub fn to_error(&self) -> Option { + /// Returns the error code if the value is an error + pub fn to_error(&self) -> Option { if self.value_type != ValueType::Error { return None; } - if self.value.is_null() { + if unsafe { self.value.error.is_null() } { return None; } - let err = unsafe { &*(self.value as *const ExtError) }; - match &err.error_type { - ErrorType::User => { - if err.message.is_null() { - return None; - } - let txt = unsafe { &*(err.message as *const TextValue) }; - Some(txt.as_str().to_string()) - } - ErrorType::ErrCode { code } => Some(format!("{}", code)), + let err = unsafe { &*self.value.error }; + Some(err.code) + } + + /// Returns the error code and optional message if the value is an error + pub fn to_error_details(&self) -> Option<(ResultCode, Option)> { + if self.value_type != ValueType::Error || unsafe { self.value.error.is_null() } { + return None; + } + let err_val = unsafe { &*(self.value.error) }; + let code = err_val.code; + + if err_val.message.is_null() { + Some((code, None)) + } else { + let txt = unsafe { &*(err_val.message as *const TextValue) }; + let msg = txt.as_str().to_owned(); + Some((code, Some(msg))) } } /// Creates a new integer Value from an i64 - pub fn from_integer(value: i64) -> Self { - let boxed = Box::new(value); + pub fn from_integer(i: i64) -> Self { Self { value_type: ValueType::Integer, - value: Box::into_raw(boxed) as *mut c_void, + value: ValueData { int: i }, } } /// Creates a new float Value from an f64 pub fn from_float(value: f64) -> Self { - let boxed = Box::new(value); Self { value_type: ValueType::Float, - value: Box::into_raw(boxed) as *mut c_void, + value: ValueData { float: value }, } } + /// Creates a new text Value from a String + /// This function allocates/leaks the string + /// and must be free'd manually pub fn from_text(s: String) -> Self { - let buffer = s.into_boxed_str(); - let ptr = buffer.as_ptr(); - let len = buffer.len(); - std::mem::forget(buffer); - let text_value = TextValue::new(ptr, len); - let text_box = Box::new(text_value); + let txt_value = TextValue::new_boxed(s); + let ptr = Box::into_raw(txt_value); Self { value_type: ValueType::Text, - value: Box::into_raw(text_box) as *mut c_void, + value: ValueData { text: ptr }, } } /// Creates a new error Value from a ResultCode - pub fn error(err: ResultCode) -> Self { - let error = ExtError { - error_type: ErrorType::ErrCode { code: err }, - message: std::ptr::null_mut(), - }; + /// This function allocates/leaks the error + /// and must be free'd manually + pub fn error(code: ResultCode) -> Self { + let err_val = ErrValue::new(code); Self { value_type: ValueType::Error, - value: Box::into_raw(Box::new(error)) as *mut c_void, + value: ValueData { + error: Box::into_raw(Box::new(err_val)) as *const ErrValue, + }, } } - /// Create a new user defined error Value with a message - pub fn custom_error(s: String) -> Self { - let buffer = s.into_boxed_str(); - let ptr = buffer.as_ptr(); - let len = buffer.len(); - std::mem::forget(buffer); - let text_value = TextValue::new(ptr, len); - let text_box = Box::new(text_value); - let error = ExtError { - error_type: ErrorType::User, - message: Box::into_raw(text_box) as *mut c_void, - }; + /// Creates a new error Value from a ResultCode and a message + /// This function allocates/leaks the error, must be free'd manually + pub fn error_with_message(message: String) -> Self { + let err_value = ErrValue::new_with_message(ResultCode::CustomError, message); + let err_box = Box::new(err_value); Self { value_type: ValueType::Error, - value: Box::into_raw(Box::new(error)) as *mut c_void, + value: ValueData { + error: Box::into_raw(err_box) as *const ErrValue, + }, } } /// Creates a new blob Value from a Vec + /// This function allocates/leaks the blob + /// and must be free'd manually pub fn from_blob(value: Vec) -> Self { let boxed = Box::new(Blob::new(value.as_ptr(), value.len() as u64)); std::mem::forget(value); Self { value_type: ValueType::Blob, - value: Box::into_raw(boxed) as *mut c_void, + value: ValueData { + blob: Box::into_raw(boxed) as *const Blob, + }, } } @@ -318,41 +376,18 @@ impl Value { /// however this does assume that the type was properly constructed with /// the appropriate value_type and value. pub unsafe fn free(self) { - if self.value.is_null() { - return; - } match self.value_type { - ValueType::Integer => { - let _ = Box::from_raw(self.value as *mut i64); - } - ValueType::Float => { - let _ = Box::from_raw(self.value as *mut f64); - } ValueType::Text => { - let _ = Box::from_raw(self.value as *mut TextValue); + let _ = Box::from_raw(self.value.text as *mut TextValue); } ValueType::Blob => { - let _ = Box::from_raw(self.value as *mut Blob); + let _ = Box::from_raw(self.value.blob as *mut Blob); } ValueType::Error => { - let _ = Box::from_raw(self.value as *mut ExtError); + let err_val = Box::from_raw(self.value.error as *mut ErrValue); + err_val.free(); } - ValueType::Null => {} + _ => {} } } } - -#[repr(C)] -pub struct ExtError { - pub error_type: ErrorType, - pub message: *mut std::ffi::c_void, -} - -#[repr(C)] -pub enum ErrorType { - User, - /// User type has a user provided message - ErrCode { - code: ResultCode, - }, -} diff --git a/extensions/percentile/Cargo.toml b/extensions/percentile/Cargo.toml index 499dec542..614578944 100644 --- a/extensions/percentile/Cargo.toml +++ b/extensions/percentile/Cargo.toml @@ -14,3 +14,6 @@ static = ["limbo_ext/static"] [dependencies] limbo_ext = { path = "../core", features = ["static"] } + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +mimalloc = { version = "*", default-features = false } diff --git a/extensions/percentile/src/lib.rs b/extensions/percentile/src/lib.rs index ceecc0e3b..9f81a6674 100644 --- a/extensions/percentile/src/lib.rs +++ b/extensions/percentile/src/lib.rs @@ -1,4 +1,4 @@ -use limbo_ext::{register_extension, AggFunc, AggregateDerive, ResultCode, Value}; +use limbo_ext::{register_extension, AggFunc, AggregateDerive, Value}; register_extension! { aggregates: { Median, Percentile, PercentileCont, PercentileDisc } @@ -41,7 +41,7 @@ impl AggFunc for Median { struct Percentile; impl AggFunc for Percentile { - type State = (Vec, Option, Option<()>); + type State = (Vec, Option, Option<&'static str>); const NAME: &'static str = "percentile"; const ARGS: i32 = 2; @@ -53,13 +53,13 @@ impl AggFunc for Percentile { args.get(1).and_then(Value::to_float), ) { if !(0.0..=100.0).contains(&p) { - err_value.get_or_insert(()); + err_value.get_or_insert("Invalid percentile value"); return; } if let Some(existing_p) = *p_value { if (existing_p - p).abs() >= 0.001 { - err_value.get_or_insert(()); + err_value.get_or_insert("Inconsistent percentile values across rows"); return; } } else { @@ -74,8 +74,8 @@ impl AggFunc for Percentile { if values.is_empty() { return Value::null(); } - if err_value.is_some() { - return Value::error(ResultCode::Error); + if let Some(err) = err_value { + return Value::error_with_message(err.into()); } if values.len() == 1 { return Value::from_float(values[0]); @@ -101,7 +101,7 @@ impl AggFunc for Percentile { struct PercentileCont; impl AggFunc for PercentileCont { - type State = (Vec, Option, Option<()>); + type State = (Vec, Option, Option<&'static str>); const NAME: &'static str = "percentile_cont"; const ARGS: i32 = 2; @@ -113,13 +113,13 @@ impl AggFunc for PercentileCont { args.get(1).and_then(Value::to_float), ) { if !(0.0..=1.0).contains(&p) { - err_state.get_or_insert(()); + err_state.get_or_insert("Percentile value must be between 0.0 and 1.0 inclusive"); return; } if let Some(existing_p) = *p_value { if (existing_p - p).abs() >= 0.001 { - err_state.get_or_insert(()); + err_state.get_or_insert("Inconsistent percentile values across rows"); return; } } else { @@ -134,8 +134,8 @@ impl AggFunc for PercentileCont { if values.is_empty() { return Value::null(); } - if err_state.is_some() { - return Value::error(ResultCode::Error); + if let Some(err) = err_state { + return Value::error_with_message(err.into()); } if values.len() == 1 { return Value::from_float(values[0]); @@ -161,7 +161,7 @@ impl AggFunc for PercentileCont { struct PercentileDisc; impl AggFunc for PercentileDisc { - type State = (Vec, Option, Option<()>); + type State = (Vec, Option, Option<&'static str>); const NAME: &'static str = "percentile_disc"; const ARGS: i32 = 2; @@ -175,8 +175,8 @@ impl AggFunc for PercentileDisc { if values.is_empty() { return Value::null(); } - if err_value.is_some() { - return Value::error(ResultCode::Error); + if let Some(err) = err_value { + return Value::error_with_message(err.into()); } let p = p_value.unwrap(); diff --git a/extensions/regexp/Cargo.toml b/extensions/regexp/Cargo.toml index 9ca5c222f..c8288e601 100644 --- a/extensions/regexp/Cargo.toml +++ b/extensions/regexp/Cargo.toml @@ -8,6 +8,7 @@ repository.workspace = true [features] static = ["limbo_ext/static"] +defaults = [] [lib] crate-type = ["cdylib", "lib"] @@ -17,3 +18,6 @@ crate-type = ["cdylib", "lib"] limbo_ext = { path = "../core", features = ["static"] } regex = "1.11.1" log = "0.4.20" + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +mimalloc = { version = "*", default-features = false } diff --git a/extensions/regexp/src/lib.rs b/extensions/regexp/src/lib.rs index a4531acbc..6f037e4d4 100644 --- a/extensions/regexp/src/lib.rs +++ b/extensions/regexp/src/lib.rs @@ -19,11 +19,11 @@ fn regex(pattern: &Value, haystack: &Value) -> Value { let Some(haystack) = haystack.to_text() else { return Value::null(); }; - let re = match Regex::new(&pattern) { + let re = match Regex::new(pattern) { Ok(re) => re, Err(_) => return Value::null(), }; - Value::from_integer(re.is_match(&haystack) as i64) + Value::from_integer(re.is_match(haystack) as i64) } _ => Value::null(), } diff --git a/extensions/uuid/Cargo.toml b/extensions/uuid/Cargo.toml index baf9a3a33..04c353d78 100644 --- a/extensions/uuid/Cargo.toml +++ b/extensions/uuid/Cargo.toml @@ -15,4 +15,6 @@ static= [ "limbo_ext/static" ] [dependencies] limbo_ext = { path = "../core", features = ["static"] } uuid = { version = "1.11.0", features = ["v4", "v7"] } -log = "0.4.20" + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +mimalloc = { version = "*", default-features = false } diff --git a/extensions/uuid/src/lib.rs b/extensions/uuid/src/lib.rs index 5a69dec76..b4f525d6f 100644 --- a/extensions/uuid/src/lib.rs +++ b/extensions/uuid/src/lib.rs @@ -1,4 +1,4 @@ -use limbo_ext::{register_extension, scalar, Value, ValueType}; +use limbo_ext::{register_extension, scalar, ResultCode, Value, ValueType}; register_extension! { scalars: {uuid4_str, uuid4_blob, uuid7_str, uuid7, uuid7_ts, uuid_str, uuid_blob }, @@ -27,25 +27,25 @@ fn uuid7_str(args: &[Value]) -> Value { ValueType::Integer => { let ctx = uuid::ContextV7::new(); let Some(int) = args[0].to_integer() else { - return Value::null(); + return Value::error(ResultCode::InvalidArgs); }; uuid::Timestamp::from_unix(ctx, int as u64, 0) } ValueType::Text => { let Some(text) = args[0].to_text() else { - return Value::null(); + return Value::error(ResultCode::InvalidArgs); }; match text.parse::() { Ok(unix) => { if unix <= 0 { - return Value::null(); + return Value::error_with_message("Invalid timestamp".to_string()); } uuid::Timestamp::from_unix(uuid::ContextV7::new(), unix as u64, 0) } - Err(_) => return Value::null(), + Err(_) => return Value::error(ResultCode::InvalidArgs), } } - _ => return Value::null(), + _ => return Value::error(ResultCode::InvalidArgs), } }; let uuid = uuid::Uuid::new_v7(timestamp); diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 53a5724ee..1e0ef421e 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -392,27 +392,32 @@ pub fn register_extension(input: TokenStream) -> TokenStream { let static_scalars = scalar_calls.clone(); let expanded = quote! { - #[cfg(feature = "static")] - pub unsafe extern "C" fn register_extension_static(api: &::limbo_ext::ExtensionApi) -> ::limbo_ext::ResultCode { - let api = unsafe { &*api }; - #(#static_scalars)* + #[cfg(not(target_family = "wasm"))] + #[cfg(not(feature = "static"))] + #[global_allocator] + static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; - #(#static_aggregates)* - - ::limbo_ext::ResultCode::OK - } - - #[cfg(not(feature = "static"))] - #[no_mangle] - pub unsafe extern "C" fn register_extension(api: &::limbo_ext::ExtensionApi) -> ::limbo_ext::ResultCode { + #[cfg(feature = "static")] + pub unsafe extern "C" fn register_extension_static(api: &::limbo_ext::ExtensionApi) -> ::limbo_ext::ResultCode { let api = unsafe { &*api }; - #(#scalar_calls)* + #(#static_scalars)* - #(#aggregate_calls)* + #(#static_aggregates)* ::limbo_ext::ResultCode::OK - } - }; + } + + #[cfg(not(feature = "static"))] + #[no_mangle] + pub unsafe extern "C" fn register_extension(api: &::limbo_ext::ExtensionApi) -> ::limbo_ext::ResultCode { + let api = unsafe { &*api }; + #(#scalar_calls)* + + #(#aggregate_calls)* + + ::limbo_ext::ResultCode::OK + } + }; TokenStream::from(expanded) }