diff --git a/Cargo.lock b/Cargo.lock index 112c3793c..0e2deb1ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1339,6 +1339,9 @@ dependencies = [ "libloading", "limbo_ext", "limbo_macros", + "limbo_percentile", + "limbo_regexp", + "limbo_uuid", "log", "miette", "mimalloc", @@ -1358,7 +1361,6 @@ dependencies = [ "sqlite3-parser", "tempfile", "thiserror 1.0.69", - "uuid", ] [[package]] diff --git a/Makefile b/Makefile index 109a3f147..69fcccf83 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ test: limbo test-compat test-sqlite3 test-shell test-extensions .PHONY: test test-extensions: limbo - cargo build --package limbo_uuid + cargo build --package limbo_regexp ./testing/extensions.py .PHONY: test-extensions diff --git a/core/Cargo.toml b/core/Cargo.toml index c378d8bcc..694617ec3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -21,8 +21,10 @@ json = [ "dep:pest", "dep:pest_derive", ] -uuid = ["dep:uuid"] +uuid = ["limbo_uuid/static"] io_uring = ["dep:io-uring", "rustix/io_uring"] +percentile = ["limbo_percentile/static"] +regexp = ["limbo_regexp/static"] [target.'cfg(target_os = "linux")'.dependencies] io-uring = { version = "0.6.1", optional = true } @@ -33,6 +35,7 @@ rustix = "0.38.34" [target.'cfg(not(target_family = "wasm"))'.dependencies] mimalloc = { version = "*", default-features = false } +libloading = "0.8.6" [dependencies] limbo_ext = { path = "../extensions/core" } @@ -57,9 +60,10 @@ pest_derive = { version = "2.0", optional = true } rand = "0.8.5" bumpalo = { version = "3.16.0", features = ["collections", "boxed"] } limbo_macros = { path = "../macros" } -uuid = { version = "1.11.0", features = ["v4", "v7"], optional = true } +limbo_uuid = { path = "../extensions/uuid", optional = true, features = ["static"] } +limbo_regexp = { path = "../extensions/regexp", optional = true, features = ["static"] } +limbo_percentile = { path = "../extensions/percentile", optional = true, features = ["static"] } miette = "7.4.0" -libloading = "0.8.6" [target.'cfg(not(target_family = "windows"))'.dev-dependencies] pprof = { version = "0.14.0", features = ["criterion", "flamegraph"] } diff --git a/core/ext/mod.rs b/core/ext/mod.rs index cf3fa6109..cbd2fa258 100644 --- a/core/ext/mod.rs +++ b/core/ext/mod.rs @@ -73,4 +73,21 @@ impl Database { register_aggregate_function, } } + + pub fn register_builtins(&self) -> Result<(), String> { + let ext_api = self.build_limbo_ext(); + #[cfg(feature = "uuid")] + if unsafe { !limbo_uuid::register_extension_static(&ext_api).is_ok() } { + return Err("Failed to register uuid extension".to_string()); + } + #[cfg(feature = "percentile")] + if unsafe { !limbo_percentile::register_extension_static(&ext_api).is_ok() } { + return Err("Failed to register percentile extension".to_string()); + } + #[cfg(feature = "regexp")] + if unsafe { !limbo_regexp::register_extension_static(&ext_api).is_ok() } { + return Err("Failed to register regexp extension".to_string()); + } + Ok(()) + } } diff --git a/core/lib.rs b/core/lib.rs index e6c812110..74557caaa 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -21,7 +21,6 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; use fallible_iterator::FallibleIterator; #[cfg(not(target_family = "wasm"))] use libloading::{Library, Symbol}; -#[cfg(not(target_family = "wasm"))] use limbo_ext::{ExtensionApi, ExtensionEntryPoint}; use log::trace; use schema::Schema; @@ -138,6 +137,9 @@ impl Database { _shared_wal: shared_wal.clone(), syms, }; + if let Err(e) = db.register_builtins() { + return Err(LimboError::ExtensionError(e)); + } let db = Arc::new(db); let conn = Rc::new(Connection { db: db.clone(), @@ -557,7 +559,6 @@ impl SymbolTable { pub fn new() -> Self { Self { functions: HashMap::new(), - // TODO: wasm libs will be very different #[cfg(not(target_family = "wasm"))] extensions: Vec::new(), } diff --git a/extensions/core/Cargo.toml b/extensions/core/Cargo.toml index f56436f5c..3194bcadb 100644 --- a/extensions/core/Cargo.toml +++ b/extensions/core/Cargo.toml @@ -6,6 +6,11 @@ edition.workspace = true license.workspace = true repository.workspace = true + +[features] +default = [] +static = [] + [dependencies] log = "0.4.20" limbo_macros = { path = "../../macros" } diff --git a/extensions/percentile/Cargo.toml b/extensions/percentile/Cargo.toml index 91340b813..499dec542 100644 --- a/extensions/percentile/Cargo.toml +++ b/extensions/percentile/Cargo.toml @@ -7,7 +7,10 @@ license.workspace = true repository.workspace = true [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "lib"] + +[features] +static = ["limbo_ext/static"] [dependencies] -limbo_ext = { path = "../core" } +limbo_ext = { path = "../core", features = ["static"] } diff --git a/extensions/regexp/Cargo.toml b/extensions/regexp/Cargo.toml index dc2f87c3b..9ca5c222f 100644 --- a/extensions/regexp/Cargo.toml +++ b/extensions/regexp/Cargo.toml @@ -6,11 +6,14 @@ edition.workspace = true license.workspace = true repository.workspace = true +[features] +static = ["limbo_ext/static"] + [lib] crate-type = ["cdylib", "lib"] [dependencies] -limbo_ext = { path = "../core"} +limbo_ext = { path = "../core", features = ["static"] } regex = "1.11.1" log = "0.4.20" diff --git a/extensions/uuid/Cargo.toml b/extensions/uuid/Cargo.toml index 2b5a80a1c..baf9a3a33 100644 --- a/extensions/uuid/Cargo.toml +++ b/extensions/uuid/Cargo.toml @@ -9,8 +9,10 @@ repository.workspace = true [lib] crate-type = ["cdylib", "lib"] +[features] +static= [ "limbo_ext/static" ] [dependencies] -limbo_ext = { path = "../core"} +limbo_ext = { path = "../core", features = ["static"] } uuid = { version = "1.11.0", features = ["v4", "v7"] } log = "0.4.20" diff --git a/extensions/uuid/src/lib.rs b/extensions/uuid/src/lib.rs index 670ba8b18..5a69dec76 100644 --- a/extensions/uuid/src/lib.rs +++ b/extensions/uuid/src/lib.rs @@ -1,7 +1,7 @@ use limbo_ext::{register_extension, scalar, Value, ValueType}; register_extension! { - scalars: { uuid4_str, uuid4_blob, uuid7_str, uuid7, uuid7_ts, uuid_str, uuid_blob } + scalars: {uuid4_str, uuid4_blob, uuid7_str, uuid7, uuid7_ts, uuid_str, uuid_blob }, } #[scalar(name = "uuid4_str", alias = "gen_random_uuid")] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 71a300337..3aa895d9b 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -12,6 +12,8 @@ description = "The Limbo database library" [lib] proc-macro = true +[features] + [dependencies] quote = "1.0.38" proc-macro2 = "1.0.93" diff --git a/macros/src/args.rs b/macros/src/args.rs index ec65be8b4..d9e59cbd3 100644 --- a/macros/src/args.rs +++ b/macros/src/args.rs @@ -17,27 +17,30 @@ impl syn::parse::Parse for RegisterExtensionInput { if input.peek(syn::Ident) && input.peek2(Token![:]) { let section_name: Ident = input.parse()?; input.parse::()?; - let content; - syn::braced!(content in input); - if section_name == "aggregates" { - aggregates = Punctuated::::parse_terminated(&content)? - .into_iter() - .collect(); - } else if section_name == "scalars" { - scalars = Punctuated::::parse_terminated(&content)? + if section_name == "aggregates" || section_name == "scalars" { + let content; + syn::braced!(content in input); + + let parsed_items = Punctuated::::parse_terminated(&content)? .into_iter() .collect(); + + if section_name == "aggregates" { + aggregates = parsed_items; + } else { + scalars = parsed_items; + } + + if input.peek(Token![,]) { + input.parse::()?; + } } else { return Err(syn::Error::new(section_name.span(), "Unknown section")); } } else { return Err(input.error("Expected aggregates: or scalars: section")); } - - if input.peek(Token![,]) { - input.parse::()?; - } } Ok(Self { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index ffb1e1524..53a5724ee 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -359,7 +359,6 @@ pub fn derive_agg_func(input: TokenStream) -> TokenStream { #[proc_macro] pub fn register_extension(input: TokenStream) -> TokenStream { let input_ast = parse_macro_input!(input as RegisterExtensionInput); - let RegisterExtensionInput { aggregates, scalars, @@ -389,17 +388,30 @@ pub fn register_extension(input: TokenStream) -> TokenStream { } } }); + let static_aggregates = aggregate_calls.clone(); + let static_scalars = scalar_calls.clone(); let expanded = quote! { - #[no_mangle] - pub 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) diff --git a/testing/extensions.py b/testing/extensions.py index 9dcd27846..74755a012 100755 --- a/testing/extensions.py +++ b/testing/extensions.py @@ -133,22 +133,7 @@ def assert_specific_time(result): def test_uuid(pipe): specific_time = "01945ca0-3189-76c0-9a8f-caf310fc8b8e" - extension_path = "./target/debug/liblimbo_uuid.so" - - # before extension loads, assert no function - run_test( - pipe, - "SELECT uuid4();", - returns_error, - "uuid functions return null when ext not loaded", - ) - run_test(pipe, "SELECT uuid4_str();", returns_error) - run_test( - pipe, - f".load {extension_path}", - returns_null, - "load extension command works properly", - ) + # these are built into the binary, so we just test they work run_test( pipe, "SELECT hex(uuid4());", @@ -286,4 +271,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main()