mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-31 22:04:23 +01:00
Fix memory issues, make extension types more efficient
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -130,38 +130,41 @@ impl OwnedValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_ffi(v: &ExtValue) -> Self {
|
||||
pub fn from_ffi(v: &ExtValue) -> Result<Self> {
|
||||
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 {
|
||||
|
||||
@@ -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<ExtValue> = 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 => {
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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<Self> {
|
||||
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<f64> {
|
||||
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<String> {
|
||||
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<i64> {
|
||||
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::<i64>().unwrap_or_default()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the error message if the value is an error
|
||||
pub fn to_error(&self) -> Option<String> {
|
||||
/// Returns the error code if the value is an error
|
||||
pub fn to_error(&self) -> Option<ResultCode> {
|
||||
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<String>)> {
|
||||
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<u8>
|
||||
/// This function allocates/leaks the blob
|
||||
/// and must be free'd manually
|
||||
pub fn from_blob(value: Vec<u8>) -> 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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<f64>, Option<f64>, Option<()>);
|
||||
type State = (Vec<f64>, Option<f64>, 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<f64>, Option<f64>, Option<()>);
|
||||
type State = (Vec<f64>, Option<f64>, 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<f64>, Option<f64>, Option<()>);
|
||||
type State = (Vec<f64>, Option<f64>, 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();
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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::<i64>() {
|
||||
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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user