diff --git a/extensions/core/README.md b/extensions/core/README.md index d3c7fa25b..6e87743e0 100644 --- a/extensions/core/README.md +++ b/extensions/core/README.md @@ -48,7 +48,7 @@ use limbo_ext::{register_extension, Value, scalar}; /// Annotate each with the scalar macro, specifying the name you would like to call it with /// and optionally, an alias.. e.g. SELECT double(4); or SELECT twice(4); -# [scalar(name = "double", alias = "twice")] +#[scalar(name = "double", alias = "twice")] fn double(&self, args: &[Value]) -> Value { if let Some(arg) = args.first() { match arg.value_type() { diff --git a/extensions/core/src/types.rs b/extensions/core/src/types.rs index 5483a4875..9d69aa942 100644 --- a/extensions/core/src/types.rs +++ b/extensions/core/src/types.rs @@ -1,5 +1,7 @@ use std::{fmt::Display, os::raw::c_void}; +/// Error type is of type ExtError which can be +/// either a user defined error or an error code #[repr(C)] pub enum ResultCode { OK = 0, @@ -150,6 +152,7 @@ impl Blob { } impl Value { + /// Creates a new Value with type Null pub fn null() -> Self { Self { value_type: ValueType::Null, @@ -157,10 +160,12 @@ impl Value { } } + /// Returns the value type of the Value pub fn value_type(&self) -> ValueType { self.value_type } + /// Returns the float value if the Value is the proper type pub fn to_float(&self) -> Option { if self.value.is_null() { return None; @@ -175,7 +180,7 @@ impl Value { _ => 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; @@ -187,6 +192,7 @@ impl Value { Some(String::from(txt.as_str())) } + /// Returns the blob value if the Value is the proper type pub fn to_blob(&self) -> Option> { if self.value_type != ValueType::Blob { return None; @@ -199,6 +205,7 @@ impl Value { 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; @@ -214,6 +221,7 @@ impl Value { } } + /// Returns the error message if the value is an error pub fn to_error(&self) -> Option { if self.value_type != ValueType::Error { return None; @@ -234,6 +242,7 @@ impl Value { } } + /// Creates a new integer Value from an i64 pub fn from_integer(value: i64) -> Self { let boxed = Box::new(value); Self { @@ -242,6 +251,7 @@ impl Value { } } + /// Creates a new float Value from an f64 pub fn from_float(value: f64) -> Self { let boxed = Box::new(value); Self { @@ -249,7 +259,7 @@ impl Value { value: Box::into_raw(boxed) as *mut c_void, } } - + /// Creates a new text Value from a String pub fn from_text(s: String) -> Self { let buffer = s.into_boxed_str(); let ptr = buffer.as_ptr(); @@ -263,6 +273,7 @@ impl Value { } } + /// Creates a new error Value from a ResultCode pub fn error(err: ResultCode) -> Self { let error = ExtError { error_type: ErrorType::ErrCode { code: err }, @@ -274,6 +285,7 @@ impl Value { } } + /// 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(); @@ -291,6 +303,7 @@ impl Value { } } + /// Creates a new blob Value from a Vec pub fn from_blob(value: Vec) -> Self { let boxed = Box::new(Blob::new(value.as_ptr(), value.len() as u64)); std::mem::forget(value); @@ -338,5 +351,8 @@ pub struct ExtError { #[repr(C)] pub enum ErrorType { User, - ErrCode { code: ResultCode }, + /// User type has a user provided message + ErrCode { + code: ResultCode, + }, } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 52de7b30c..ffb1e1524 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -138,6 +138,29 @@ fn generate_get_description( enum_impl.parse().unwrap() } +/// Declare a scalar function for your extension. This requires the name: +/// #[scalar(name = "example")] of what you wish to call your function with. +/// Your function __must__ use the signature: `fn (args: &[Value]) -> Value` +/// with proper spelling. +/// ```ignore +/// use limbo_ext::{scalar, Value}; +/// #[scalar(name = "double", alias = "twice")] // you can provide an alias +/// fn double(args: &[Value]) -> Value { +/// match arg.value_type() { +/// ValueType::Float => { +/// let val = arg.to_float().unwrap(); +/// Value::from_float(val * 2.0) +/// } +/// ValueType::Integer => { +/// let val = arg.to_integer().unwrap(); +/// Value::from_integer(val * 2) +/// } +/// } +/// } else { +/// Value::null() +/// } +/// } +/// ``` #[proc_macro_attribute] pub fn scalar(attr: TokenStream, input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as ItemFn); @@ -199,6 +222,29 @@ pub fn scalar(attr: TokenStream, input: TokenStream) -> TokenStream { TokenStream::from(expanded) } +/// Define an aggregate function for your extension by deriving +/// AggregateDerive on a struct that implements the AggFunc trait. +/// ```ignore +/// use limbo_ext::{register_extension, Value, AggregateDerive, AggFunc}; +/// +///#[derive(AggregateDerive)] +///struct SumPlusOne; +/// +///impl AggFunc for SumPlusOne { +/// type State = i64; +/// const NAME: &'static str = "sum_plus_one"; +/// const ARGS: i32 = 1; +/// fn step(state: &mut Self::State, args: &[Value]) { +/// let Some(val) = args[0].to_integer() else { +/// return; +/// }; +/// *state += val; +/// } +/// fn finalize(state: Self::State) -> Value { +/// Value::from_integer(state + 1) +/// } +///} +/// ``` #[proc_macro_derive(AggregateDerive)] pub fn derive_agg_func(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); @@ -278,6 +324,38 @@ pub fn derive_agg_func(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } +/// Register your extension with 'core' by providing the relevant functions +///```ignore +///use limbo_ext::{register_extension, scalar, Value, AggregateDerive, AggFunc}; +/// +/// register_extension!{ scalars: { return_one }, aggregates: { SumPlusOne } } +/// +///#[scalar(name = "one")] +///fn return_one(args: &[Value]) -> Value { +/// return Value::from_integer(1); +///} +/// +///#[derive(AggregateDerive)] +///struct SumPlusOne; +/// +///impl AggFunc for SumPlusOne { +/// type State = i64; +/// const NAME: &'static str = "sum_plus_one"; +/// const ARGS: i32 = 1; +/// +/// fn step(state: &mut Self::State, args: &[Value]) { +/// let Some(val) = args[0].to_integer() else { +/// return; +/// }; +/// *state += val; +/// } +/// +/// fn finalize(state: Self::State) -> Value { +/// Value::from_integer(state + 1) +/// } +///} +/// +/// ``` #[proc_macro] pub fn register_extension(input: TokenStream) -> TokenStream { let input_ast = parse_macro_input!(input as RegisterExtensionInput);