Fix memory issues, make extension types more efficient

This commit is contained in:
PThorpe92
2025-01-27 22:11:34 -05:00
parent ad8e05b9a1
commit 793cdf8bad
12 changed files with 261 additions and 184 deletions

4
Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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 {

View File

@@ -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 => {

View File

@@ -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`

View File

@@ -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,
},
}

View File

@@ -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 }

View File

@@ -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();

View File

@@ -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 }

View File

@@ -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(),
}

View File

@@ -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 }

View File

@@ -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);

View File

@@ -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)
}