├── .gitignore ├── examples ├── wordcount.rs ├── len.rs ├── add.rs ├── commas.rs ├── keys.rs ├── dbl.rs ├── udt.rs ├── fib.rs ├── combine.rs ├── Cargo.toml └── topn.rs ├── tests ├── Cargo.toml └── hygiene.rs ├── Cargo.toml ├── scylla-udf-macros ├── Cargo.toml └── src │ ├── lib.rs │ ├── path.rs │ ├── export_udt.rs │ ├── export_udf.rs │ └── export_newtype.rs ├── scylla-udf ├── Cargo.toml └── src │ ├── abi_exports.rs │ ├── from_wasmptr.rs │ ├── to_wasmptr.rs │ ├── wasmptr.rs │ ├── lib.rs │ ├── to_columntype.rs │ └── wasm_convertible.rs ├── .github ├── pull_request_template.md └── workflows │ └── rust.yml ├── README.md └── Cargo.lock.msrv /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | -------------------------------------------------------------------------------- /examples/wordcount.rs: -------------------------------------------------------------------------------- 1 | use scylla_udf::export_udf; 2 | 3 | #[export_udf] 4 | fn wordcount(text: String) -> i32 { 5 | text.split(' ').count() as i32 6 | } 7 | -------------------------------------------------------------------------------- /examples/len.rs: -------------------------------------------------------------------------------- 1 | use scylla_udf::export_udf; 2 | 3 | #[export_udf] 4 | fn len(strings: std::collections::BTreeSet) -> i16 { 5 | strings.len() as i16 6 | } 7 | -------------------------------------------------------------------------------- /examples/add.rs: -------------------------------------------------------------------------------- 1 | use scylla_udf::export_udf; 2 | 3 | type SmallInt = i16; 4 | 5 | #[export_udf] 6 | fn add(i1: SmallInt, i2: SmallInt) -> SmallInt { 7 | i1 + i2 8 | } 9 | -------------------------------------------------------------------------------- /examples/commas.rs: -------------------------------------------------------------------------------- 1 | use scylla_udf::export_udf; 2 | 3 | #[export_udf] 4 | fn commas(strings: Option>) -> Option { 5 | strings.map(|strings| strings.join(", ")) 6 | } 7 | -------------------------------------------------------------------------------- /examples/keys.rs: -------------------------------------------------------------------------------- 1 | use scylla_udf::export_udf; 2 | 3 | #[export_udf] 4 | fn keys(map: std::collections::BTreeMap) -> Vec { 5 | map.into_keys().collect() 6 | } 7 | -------------------------------------------------------------------------------- /examples/dbl.rs: -------------------------------------------------------------------------------- 1 | use scylla_udf::export_udf; 2 | 3 | #[export_udf] 4 | fn dbl(s: String) -> String { 5 | let mut newstr = String::new(); 6 | newstr.push_str(&s); 7 | newstr.push_str(&s); 8 | newstr 9 | } 10 | -------------------------------------------------------------------------------- /examples/udt.rs: -------------------------------------------------------------------------------- 1 | use scylla_udf::*; 2 | 3 | #[export_udt] 4 | struct Udt { 5 | a: i32, 6 | b: i32, 7 | c: String, 8 | d: String, 9 | } 10 | 11 | #[export_udf] 12 | fn udt(arg: Udt) -> Udt { 13 | Udt { 14 | a: arg.b, 15 | b: arg.a, 16 | c: arg.d, 17 | d: arg.c, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/fib.rs: -------------------------------------------------------------------------------- 1 | use scylla_udf::*; 2 | 3 | #[export_newtype] 4 | struct FibInputNumber(i32); 5 | 6 | #[export_newtype] 7 | struct FibReturnNumber(i64); 8 | 9 | #[export_udf] 10 | fn fib(i: FibInputNumber) -> FibReturnNumber { 11 | FibReturnNumber(if i.0 <= 2 { 12 | 1 13 | } else { 14 | fib(FibInputNumber(i.0 - 1)).0 + fib(FibInputNumber(i.0 - 2)).0 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | version = "0.0.0" 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | publish = false 7 | 8 | [dependencies] 9 | scylla-udf = { version = "0.1.0", path = "../scylla-udf" } 10 | bigdecimal = "0.2.0" 11 | bytes = "1.2.1" 12 | chrono = "0.4" 13 | libc = "0.2.119" 14 | num-bigint = "0.3" 15 | uuid = "1.0" 16 | 17 | [[test]] 18 | name = "hygiene" 19 | path = "hygiene.rs" 20 | crate-type = ["cdylib"] 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "examples", 4 | "scylla-udf", 5 | "scylla-udf-macros", 6 | "tests", 7 | ] 8 | 9 | [workspace.package] 10 | edition = "2021" 11 | version = "0.0.1" 12 | repository = "https://github.com/scylladb/scylla-rust-udf" 13 | license = "MIT OR Apache-2.0" 14 | rust-version = "1.66.1" 15 | 16 | [workspace.dependencies] 17 | scylla-udf = { path = "scylla-udf" } 18 | scylla-udf-macros = { path = "scylla-udf-macros" } 19 | -------------------------------------------------------------------------------- /scylla-udf-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scylla-udf-macros" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | repository.workspace = true 6 | license.workspace = true 7 | rust-version.workspace = true 8 | description = "Implementation of scylla-udf macros" 9 | readme = "../README.md" 10 | keywords = ["scylla", "udf", "macro"] 11 | categories = ["database", "wasm"] 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | proc-macro2 = "1.0.36" 18 | quote = "1.0.15" 19 | syn = { version = "1.0.86", features = ["full"] } 20 | -------------------------------------------------------------------------------- /scylla-udf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scylla-udf" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | repository.workspace = true 6 | license.workspace = true 7 | rust-version.workspace = true 8 | description = "Proc macros for scylla rust UDFs bindings" 9 | readme = "../README.md" 10 | keywords = ["scylla", "udf"] 11 | categories = ["database", "wasm"] 12 | 13 | [dependencies] 14 | bigdecimal = "0.2.0" 15 | bytes = "1.2.1" 16 | chrono = "0.4" 17 | libc = "0.2.119" 18 | num-bigint = "0.3" 19 | scylla-udf-macros = { version = "0.1.0", path = "../scylla-udf-macros" } 20 | scylla-cql = "0.0.4" 21 | uuid = "1.0" 22 | -------------------------------------------------------------------------------- /scylla-udf-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | mod export_newtype; 4 | mod export_udf; 5 | mod export_udt; 6 | 7 | #[proc_macro_attribute] 8 | pub fn export_udt(attrs: TokenStream, item: TokenStream) -> TokenStream { 9 | export_udt::export_udt(attrs, item) 10 | } 11 | 12 | #[proc_macro_attribute] 13 | pub fn export_udf(attrs: TokenStream, item: TokenStream) -> TokenStream { 14 | export_udf::export_udf(attrs, item) 15 | } 16 | 17 | #[proc_macro_attribute] 18 | pub fn export_newtype(attrs: TokenStream, item: TokenStream) -> TokenStream { 19 | export_newtype::export_newtype(attrs, item) 20 | } 21 | 22 | pub(crate) mod path; 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Pre-review checklist 2 | 3 | 8 | 9 | - [ ] I have split my patch into logically separate commits. 10 | - [ ] All commit messages clearly explain what they change and why. 11 | - [ ] I added relevant tests for new features and bug fixes. 12 | - [ ] All commits compile, pass static checks and pass tests. 13 | - [ ] PR description sums up the changes and reasons why they should be introduced. 14 | - [ ] I added appropriate `Fixes:` annotations to PR description. 15 | -------------------------------------------------------------------------------- /scylla-udf/src/abi_exports.rs: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | fn malloc(size: u32) -> *mut u8; 3 | fn free(ptr: *mut u8); 4 | } 5 | 6 | /// # Safety 7 | /// - caller must ensure that the size is valid, if the allocation fails 8 | /// - the caller must not dereference the returned pointer 9 | #[no_mangle] 10 | #[doc(hidden)] 11 | pub(crate) unsafe extern "C" fn _scylla_malloc(size: u32) -> u32 { 12 | malloc(size) as u32 13 | } 14 | 15 | /// # Safety 16 | /// - caller must ensure that the pointer is valid 17 | #[no_mangle] 18 | #[doc(hidden)] 19 | pub(crate) unsafe extern "C" fn _scylla_free(ptr: u32) { 20 | free(ptr as *mut u8) 21 | } 22 | 23 | #[no_mangle] 24 | #[doc(hidden)] 25 | static _scylla_abi: u32 = 2; 26 | -------------------------------------------------------------------------------- /scylla-udf/src/from_wasmptr.rs: -------------------------------------------------------------------------------- 1 | use crate::to_columntype::ToColumnType; 2 | use crate::wasmptr::WasmPtr; 3 | use scylla_cql::cql_to_rust::FromCqlVal; 4 | use scylla_cql::frame::response::result::{deser_cql_value, CqlValue}; 5 | 6 | pub trait FromWasmPtr { 7 | fn from_wasmptr(wasmptr: WasmPtr) -> Self; 8 | } 9 | 10 | impl FromWasmPtr for T 11 | where 12 | T: FromCqlVal> + ToColumnType, 13 | { 14 | fn from_wasmptr(wasmptr: WasmPtr) -> Self { 15 | if wasmptr.is_null() { 16 | return T::from_cql(None).unwrap(); 17 | } 18 | let mut slice = wasmptr.as_slice().expect("WasmPtr::as_slice returned None"); 19 | T::from_cql(Some( 20 | deser_cql_value(&T::to_column_type(), &mut slice).unwrap(), 21 | )) 22 | .unwrap() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scylla-udf/src/to_wasmptr.rs: -------------------------------------------------------------------------------- 1 | use crate::wasmptr::WasmPtr; 2 | use core::convert::TryInto; 3 | use scylla_cql::frame::value::Value; 4 | 5 | pub trait ToWasmPtr { 6 | fn to_wasmptr(&self) -> WasmPtr; 7 | } 8 | 9 | impl ToWasmPtr for T { 10 | fn to_wasmptr(&self) -> WasmPtr { 11 | let mut bytes = Vec::::new(); 12 | self.serialize(&mut bytes).expect("Error serializing value"); 13 | let size = u32::from_be_bytes(bytes[..4].try_into().expect("slice with incorrect length")); 14 | if size == u32::MAX { 15 | return WasmPtr::null(); 16 | } 17 | let mut dest = WasmPtr::with_size(size).expect("Failed to allocate memory"); 18 | let dest_slice = dest 19 | .as_mut_slice() 20 | .expect("WasmPtr::as_mut_slice returned None"); 21 | dest_slice.copy_from_slice(&bytes[4..]); 22 | dest 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/combine.rs: -------------------------------------------------------------------------------- 1 | use scylla_udf::{export_udf, CqlDuration, Time, Timestamp}; 2 | 3 | #[allow(clippy::too_many_arguments, clippy::type_complexity)] 4 | #[export_udf] 5 | fn combine( 6 | b: bool, 7 | blob: Vec, 8 | date: chrono::NaiveDate, 9 | bd: bigdecimal::BigDecimal, 10 | dbl: f64, 11 | cqldur: CqlDuration, 12 | flt: f32, 13 | int32: i32, 14 | int64: i64, 15 | s: String, 16 | tstamp: Timestamp, 17 | ip: std::net::IpAddr, 18 | int16: i16, 19 | int8: i8, 20 | tim: Time, 21 | uid: uuid::Uuid, 22 | bi: num_bigint::BigInt, 23 | ) -> ( 24 | ( 25 | bool, 26 | Vec, 27 | chrono::NaiveDate, 28 | bigdecimal::BigDecimal, 29 | f64, 30 | CqlDuration, 31 | f32, 32 | i32, 33 | i64, 34 | ), 35 | ( 36 | String, 37 | Timestamp, 38 | std::net::IpAddr, 39 | i16, 40 | i8, 41 | Time, 42 | uuid::Uuid, 43 | num_bigint::BigInt, 44 | ), 45 | ) { 46 | ( 47 | (b, blob, date, bd, dbl, cqldur, flt, int32, int64), 48 | (s, tstamp, ip, int16, int8, tim, uid, bi), 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /tests/hygiene.rs: -------------------------------------------------------------------------------- 1 | #![no_implicit_prelude] 2 | 3 | extern crate scylla_udf as _scylla_udf; 4 | 5 | #[derive(::core::fmt::Debug, ::core::cmp::PartialEq, ::std::marker::Copy, ::std::clone::Clone)] 6 | #[::_scylla_udf::export_udt(crate = "_scylla_udf")] 7 | struct TestStruct { 8 | a: ::core::primitive::i32, 9 | } 10 | #[derive(::core::fmt::Debug, ::core::cmp::PartialEq, ::std::marker::Copy, ::std::clone::Clone)] 11 | #[::_scylla_udf::export_newtype(crate = "_scylla_udf")] 12 | struct TestNewtype(::core::primitive::i32); 13 | 14 | // Macro can only be expanded if TestStruct and TestNewtype were 15 | // properly expanded. 16 | #[::_scylla_udf::export_udf(crate = "_scylla_udf")] 17 | fn test_fn(arg1: TestNewtype, arg2: TestStruct) -> (TestNewtype, TestStruct) { 18 | (arg1, arg2) 19 | } 20 | 21 | #[test] 22 | fn test_renamed() { 23 | use ::_scylla_udf::_macro_internal::WasmConvertible; 24 | let arg1 = TestNewtype(16); 25 | let arg2 = TestStruct { a: 16 }; 26 | let rets = _scylla_internal_test_fn(arg1.to_wasm(), arg2.to_wasm()); 27 | let (ret1, ret2) = <(TestNewtype, TestStruct)>::from_wasm(rets); 28 | ::std::assert_eq!(arg1, ret1); 29 | ::std::assert_eq!(arg2, ret2); 30 | } 31 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.0.0" 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | publish = false 7 | 8 | [dependencies] 9 | chrono = "0.4" 10 | bigdecimal = "0.2.0" 11 | num-bigint = "0.3" 12 | scylla-udf = { version = "0.1.0", path = "../scylla-udf" } 13 | uuid = "1.0" 14 | 15 | [[example]] 16 | name = "add" 17 | path = "add.rs" 18 | crate-type = ["cdylib"] 19 | 20 | [[example]] 21 | name = "combine" 22 | path = "combine.rs" 23 | crate-type = ["cdylib"] 24 | 25 | [[example]] 26 | name = "commas" 27 | path = "commas.rs" 28 | crate-type = ["cdylib"] 29 | 30 | [[example]] 31 | name = "dbl" 32 | path = "dbl.rs" 33 | crate-type = ["cdylib"] 34 | 35 | [[example]] 36 | name = "fib" 37 | path = "fib.rs" 38 | crate-type = ["cdylib"] 39 | 40 | [[example]] 41 | name = "keys" 42 | path = "keys.rs" 43 | crate-type = ["cdylib"] 44 | 45 | [[example]] 46 | name = "len" 47 | path = "len.rs" 48 | crate-type = ["cdylib"] 49 | 50 | [[example]] 51 | name = "topn" 52 | path = "topn.rs" 53 | crate-type = ["cdylib"] 54 | 55 | [[example]] 56 | name = "udt" 57 | path = "udt.rs" 58 | crate-type = ["cdylib"] 59 | 60 | [[example]] 61 | name = "wordcount" 62 | path = "wordcount.rs" 63 | crate-type = ["cdylib"] 64 | -------------------------------------------------------------------------------- /examples/topn.rs: -------------------------------------------------------------------------------- 1 | use scylla_udf::*; 2 | use std::collections::BTreeSet; 3 | 4 | #[export_newtype] 5 | struct StringLen(String); 6 | 7 | impl std::cmp::PartialEq for StringLen { 8 | fn eq(&self, other: &Self) -> bool { 9 | self.0 == other.0 10 | } 11 | } 12 | 13 | impl std::cmp::Eq for StringLen {} 14 | 15 | impl std::cmp::PartialOrd for StringLen { 16 | fn partial_cmp(&self, other: &Self) -> Option { 17 | Some(self.cmp(other)) 18 | } 19 | } 20 | 21 | impl std::cmp::Ord for StringLen { 22 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 23 | if self.0.len().cmp(&other.0.len()) == std::cmp::Ordering::Equal { 24 | self.0.cmp(&other.0) 25 | } else { 26 | self.0.len().cmp(&other.0.len()) 27 | } 28 | } 29 | } 30 | 31 | // Store the top N strings by length, without repetitions. 32 | #[export_udf] 33 | fn topn_row( 34 | acc_tup: Option<(i32, BTreeSet)>, 35 | v: Option, 36 | ) -> Option<(i32, BTreeSet)> { 37 | if let Some((n, mut acc)) = acc_tup { 38 | if let Some(v) = v { 39 | acc.insert(v); 40 | while acc.len() > n as usize { 41 | acc.pop_first(); 42 | } 43 | } 44 | Some((n, acc)) 45 | } else { 46 | None 47 | } 48 | } 49 | 50 | #[export_udf] 51 | fn topn_reduce( 52 | (n1, mut acc1): (i32, BTreeSet), 53 | (n2, mut acc2): (i32, BTreeSet), 54 | ) -> (i32, BTreeSet) { 55 | assert!(n1 == n2); 56 | acc1.append(&mut acc2); 57 | while acc1.len() > n1 as usize { 58 | acc1.pop_first(); 59 | } 60 | (n1, acc1) 61 | } 62 | 63 | #[export_udf] 64 | fn topn_final((_, acc): (i32, BTreeSet)) -> BTreeSet { 65 | acc 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | RUSTFLAGS: -Dwarnings 12 | rust_min: 1.66.1 # <- Update this when bumping up MSRV 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Format check 20 | run: cargo fmt --verbose --all -- --check 21 | - name: Setup wasm32-wasip1 22 | run: rustup target add wasm32-wasip1 23 | - name: Clippy check 24 | run: cargo clippy --verbose --all-targets --target=wasm32-wasip1 -- -Aclippy::uninlined_format_args 25 | - name: Build 26 | run: cargo build --target=wasm32-wasip1 --verbose --all-targets 27 | 28 | test: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v3 32 | - name: Setup wasm32-wasip1 33 | run: rustup target add wasm32-wasip1 34 | - name: Setup wasmtime 35 | run: | 36 | set -o pipefail 37 | curl https://wasmtime.dev/install.sh -sSf | bash 38 | - name: Test 39 | run: CARGO_TARGET_WASM32_WASIP1_RUNNER="$HOME/.wasmtime/bin/wasmtime -W unknown-exports-allow=y" cargo test --target=wasm32-wasip1 --all-targets 40 | 41 | # Tests that our current minimum supported rust version compiles everything sucessfully 42 | # Note: Until Rust 1.77, the WASI target name was wasm32-wasi. When bumping MSRV update the name to wasm32-wasip1 43 | min_rust: 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v3 47 | - name: Install Rust ${{ env.rust_min }} 48 | uses: dtolnay/rust-toolchain@master 49 | with: 50 | toolchain: ${{ env.rust_min }} 51 | - name: Print Rust version 52 | run: rustc --version 53 | - name: Use MSRV Cargo.lock 54 | run: cp Cargo.lock.msrv Cargo.lock 55 | - name: Setup wasm32-wasi 56 | run: rustup target add wasm32-wasi 57 | - name: MSRV cargo check 58 | run: cargo check --verbose --all-targets --target=wasm32-wasi --locked 59 | 60 | # Tests that docstrings generate docs without warnings 61 | cargo_docs: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v3 65 | - name: Compile docs 66 | run: RUSTDOCFLAGS=-Dwarnings cargo doc 67 | -------------------------------------------------------------------------------- /scylla-udf/src/wasmptr.rs: -------------------------------------------------------------------------------- 1 | use crate::abi_exports::{_scylla_free, _scylla_malloc}; 2 | 3 | // A unique pointer to an object in the WASM memory. 4 | // Contains the serialized size of the object in the high 32 bits, and the pointer 5 | // to the object in the low 32 bits. A null pointer is represented by a size of u32::MAX. 6 | // The pointer is allocated with _scylla_malloc and freed with _scylla_free. 7 | #[repr(transparent)] 8 | pub struct WasmPtr(u64); 9 | 10 | impl WasmPtr { 11 | pub fn with_size(size: u32) -> Option { 12 | if size == u32::MAX { 13 | // u32::MAX is reserved for null 14 | return None; 15 | } 16 | 17 | // SAFETY: the size fits in a u32, so it's valid to allocate that much memory 18 | // and we do not dereference the pointer if the allocation fails 19 | let ptr = unsafe { _scylla_malloc(size) }; 20 | if ptr == 0 { 21 | return None; 22 | } 23 | Some(WasmPtr(((size as u64) << 32) + ptr as u64)) 24 | } 25 | 26 | pub const fn size(&self) -> Option { 27 | let size = self.0 >> 32; 28 | if size == u32::MAX as u64 { 29 | None 30 | } else { 31 | Some(size as usize) 32 | } 33 | } 34 | 35 | pub const fn null() -> WasmPtr { 36 | WasmPtr((u32::MAX as u64) << 32) 37 | } 38 | 39 | pub const fn is_null(&self) -> bool { 40 | self.size().is_none() 41 | } 42 | 43 | fn raw(&self) -> *mut u8 { 44 | (self.0 & 0xffffffff) as *mut u8 45 | } 46 | 47 | fn raw_mut(&self) -> *mut u8 { 48 | (self.0 & 0xffffffff) as *mut u8 49 | } 50 | 51 | pub fn as_slice<'a>(&self) -> Option<&'a [u8]> { 52 | // SAFETY: the `dest` pointer is a succesful result of allocating `size` bytes and it's always aligned to a u8 53 | self.size() 54 | .map(|size| unsafe { std::slice::from_raw_parts::<'a>(self.raw(), size) }) 55 | } 56 | 57 | pub fn as_mut_slice<'a>(&mut self) -> Option<&'a mut [u8]> { 58 | if let Some(size) = self.size() { 59 | // SAFETY: the `dest` pointer is a succesful result of allocating `size` bytes and it's always aligned to a u8 60 | Some(unsafe { std::slice::from_raw_parts_mut::<'a>(self.raw_mut(), size) }) 61 | } else { 62 | None 63 | } 64 | } 65 | } 66 | 67 | impl Drop for WasmPtr { 68 | fn drop(&mut self) { 69 | if !self.is_null() { 70 | // SAFETY: the `dest` pointer is a succesful result of a _scylla_malloc call, so it's valid 71 | unsafe { _scylla_free(self.raw() as u32) }; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /scylla-udf-macros/src/path.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream as TokenStream2; 2 | use syn::{AttributeArgs, Error, Lit, Meta, NestedMeta}; 3 | 4 | // function that returns the value of the "crate" attribute given AttributeArgs 5 | pub(crate) fn get_path_no_internal(atrs: &AttributeArgs) -> Result { 6 | let mut this_path: Option = None; 7 | for attr in atrs.iter() { 8 | match attr { 9 | NestedMeta::Lit(lit) => { 10 | return Err(Error::new_spanned( 11 | lit, 12 | "unexpected literal attribute for `scylla_udf`", 13 | )); 14 | } 15 | NestedMeta::Meta(meta) => { 16 | if !meta.path().is_ident("crate") { 17 | return Err(Error::new_spanned( 18 | meta, 19 | "unexpected meta attribute for `scylla_udf`", 20 | )); 21 | } 22 | match meta { 23 | Meta::NameValue(meta_name_value) => { 24 | if let Lit::Str(lit_str) = &meta_name_value.lit { 25 | let path_val = 26 | &lit_str.value().parse::().unwrap(); 27 | if this_path.is_none() { 28 | this_path = Some(quote::quote!(#path_val)); 29 | } else { 30 | return Err(syn::Error::new_spanned( 31 | &meta_name_value.lit, 32 | "the `crate` attribute was set multiple times", 33 | )); 34 | } 35 | } else { 36 | return Err(syn::Error::new_spanned( 37 | &meta_name_value.lit, 38 | "the `crate` attribute should be a string literal", 39 | )); 40 | } 41 | } 42 | other => { 43 | return Err(Error::new_spanned( 44 | other, 45 | "the `crate` attribute have a single value", 46 | )); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | Ok(this_path.unwrap_or_else(|| quote::quote!(scylla_udf))) 53 | } 54 | 55 | pub(crate) fn append_internal(path: &TokenStream2) -> TokenStream2 { 56 | quote::quote!(#path::_macro_internal) 57 | } 58 | 59 | pub(crate) fn get_path(atrs: &AttributeArgs) -> Result { 60 | let path = get_path_no_internal(atrs)?; 61 | Ok(append_internal(&path)) 62 | } 63 | -------------------------------------------------------------------------------- /scylla-udf/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod abi_exports; 2 | mod from_wasmptr; 3 | mod to_columntype; 4 | mod to_wasmptr; 5 | mod wasm_convertible; 6 | mod wasmptr; 7 | 8 | /// Not a part of public API. May change in minor releases. 9 | /// Contains all the items used by the scylla_udf macros. 10 | #[doc(hidden)] 11 | pub mod _macro_internal { 12 | pub use crate::from_wasmptr::FromWasmPtr; 13 | pub use crate::to_columntype::ToColumnType; 14 | pub use crate::to_wasmptr::ToWasmPtr; 15 | pub use crate::wasm_convertible::WasmConvertible; 16 | pub use crate::wasmptr::WasmPtr; 17 | pub use scylla_cql::_macro_internal::*; 18 | pub use scylla_cql::frame::response::result::ColumnType; 19 | } 20 | 21 | /// This macro allows using a Rust function as a Scylla UDF. 22 | /// 23 | /// The function must have arguments and return value of Rust types that can be mapped to CQL types, 24 | /// the macro takes care of converting the arguments from CQL types to Rust types and back. 25 | /// The function must not have the `#[no_mangle]` attribute, it will be added by the macro. 26 | /// 27 | /// For example, for a function: 28 | /// ``` 29 | /// #[scylla_udf::export_udf] 30 | /// fn foo(arg: i32) -> i32 { 31 | /// arg + 1 32 | /// } 33 | /// ``` 34 | /// you can use the compiled binary in its text format as a UDF in Scylla: 35 | /// ```text 36 | /// CREATE FUNCTION foo(arg int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE rust AS '(module ...)`; 37 | /// ``` 38 | pub use scylla_udf_macros::export_udf; 39 | 40 | /// This macro allows mapping a Rust struct to a UDT from Scylla, and using in a scylla_udf function. 41 | /// 42 | /// To use it, you need to define a struct with the same fields as the Scylla UDT. 43 | /// For example, for a UDT defined as: 44 | /// ```text 45 | /// CREATE TYPE udt ( 46 | /// a int, 47 | /// b double, 48 | /// c text, 49 | /// ); 50 | /// ``` 51 | /// you need to define a struct: 52 | /// ``` 53 | /// #[scylla_udf::export_udt] 54 | /// struct Udt { 55 | /// a: i32, 56 | /// b: f64, 57 | /// c: String, 58 | /// } 59 | /// ``` 60 | pub use scylla_udf_macros::export_udt; 61 | 62 | /// This macro allows (de)serializing a cql type to/from a Rust "newtype" struct. 63 | /// 64 | /// The macro takes a "newtype" struct (tuple struct with only one field) and generates all implementations for (de)serialization 65 | /// traits used in the scylla_udf macros by treating the struct as the inner type itself. 66 | /// 67 | /// This allows overriding the impls for the inner type, while still being able to use it in the types of parameters or return 68 | /// values of scylla_udf functions. 69 | /// 70 | /// For example, for a function using a newtype struct: 71 | /// ``` 72 | /// #[scylla_udf::export_newtype] 73 | /// struct MyInt(i32); 74 | /// 75 | /// #[scylla_udf::export_udf] 76 | /// fn foo(arg: MyInt) -> MyInt { 77 | /// ... 78 | /// } 79 | /// ``` 80 | /// and a table: 81 | /// ```text 82 | /// CREATE TABLE table (x int PRIMARY KEY); 83 | /// ``` 84 | /// you can use the function in a query: 85 | /// ```text 86 | /// SELECT foo(x) FROM table; 87 | /// ``` 88 | pub use scylla_udf_macros::export_newtype; 89 | 90 | pub use scylla_cql::frame::value::{Counter, CqlDuration, Time, Timestamp}; 91 | -------------------------------------------------------------------------------- /scylla-udf-macros/src/export_udt.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::TokenStream as TokenStream2; 3 | use quote::{quote, quote_spanned}; 4 | use syn::spanned::Spanned; 5 | use syn::Fields; 6 | 7 | pub fn impl_wasm_convertible(st: &syn::ItemStruct, path: &TokenStream2) -> TokenStream2 { 8 | let struct_name = &st.ident; 9 | let (impl_generics, ty_generics, where_clause) = st.generics.split_for_impl(); 10 | quote! { 11 | impl #impl_generics ::#path::WasmConvertible for #struct_name #ty_generics #where_clause { 12 | type WasmType = ::#path::WasmPtr; 13 | fn from_wasm(arg: Self::WasmType) -> Self { 14 | ::from_wasmptr(arg) 15 | } 16 | fn to_wasm(&self) -> Self::WasmType { 17 | ::to_wasmptr(self) 18 | } 19 | } 20 | } 21 | } 22 | 23 | pub fn impl_to_col_type(st: &syn::ItemStruct, path: &TokenStream2) -> TokenStream2 { 24 | let struct_name = &st.ident; 25 | let struct_name_string = struct_name.to_string(); 26 | let struct_fields = match &st.fields { 27 | Fields::Named(named_fields) => named_fields, 28 | _ => { 29 | return syn::Error::new_spanned( 30 | st, 31 | "#[scylla_udf::export_udt] works only for structs with named fields.", 32 | ) 33 | .to_compile_error() 34 | } 35 | }; 36 | let (impl_generics, ty_generics, where_clause) = st.generics.split_for_impl(); 37 | let fields_column_types = struct_fields.named.iter().map(|field| { 38 | // we matched with Fields::Named above, so we can unwrap 39 | let field_name = field.ident.as_ref().unwrap().to_string(); 40 | let field_type = &field.ty; 41 | quote_spanned! {field.span() => 42 | (#field_name.to_string(), <#field_type as ::#path::ToColumnType>::to_column_type()), 43 | } 44 | }); 45 | quote! { 46 | impl #impl_generics ::#path::ToColumnType for #struct_name #ty_generics #where_clause { 47 | fn to_column_type() -> ::#path::ColumnType { 48 | use ::std::string::ToString; 49 | ::#path::ColumnType::UserDefinedType { 50 | type_name: #struct_name_string.to_string(), 51 | keyspace: "".to_string(), 52 | field_types: ::std::vec![#(#fields_column_types)*], 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | pub(crate) fn export_udt(attrs: TokenStream, item: TokenStream) -> TokenStream { 60 | let st = syn::parse_macro_input!(item as syn::ItemStruct); 61 | let atrs = syn::parse_macro_input!(attrs as syn::AttributeArgs); 62 | let path_no_internal = crate::path::get_path_no_internal(&atrs) 63 | .expect("Couldn't get path to the scylla_udf crate"); 64 | let path_string = path_no_internal.to_string(); 65 | let path = crate::path::append_internal(&path_no_internal); 66 | let wasm_convertible = impl_wasm_convertible(&st, &path); 67 | let to_col_type = impl_to_col_type(&st, &path); 68 | quote! { 69 | #[derive(::#path::FromUserType)] 70 | #[derive(::#path::IntoUserType)] 71 | #[scylla_crate = #path_string] 72 | #st 73 | #wasm_convertible 74 | #to_col_type 75 | } 76 | .into() 77 | } 78 | -------------------------------------------------------------------------------- /scylla-udf/src/to_columntype.rs: -------------------------------------------------------------------------------- 1 | pub use scylla_cql::frame::response::result::ColumnType; 2 | use scylla_cql::frame::value::{Counter, CqlDuration, Time, Timestamp}; 3 | use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; 4 | 5 | pub trait ToColumnType { 6 | fn to_column_type() -> ColumnType; 7 | } 8 | 9 | // This macro implements ToColumnType given a Rust type and the resulting ColumnType 10 | macro_rules! impl_to_col_type { 11 | ($rust_type:ty, $col_type:expr) => { 12 | impl ToColumnType for $rust_type { 13 | fn to_column_type() -> ColumnType { 14 | $col_type 15 | } 16 | } 17 | }; 18 | } 19 | 20 | impl_to_col_type!(bool, ColumnType::Boolean); 21 | impl_to_col_type!(Vec, ColumnType::Blob); 22 | impl_to_col_type!(Counter, ColumnType::Counter); 23 | impl_to_col_type!(chrono::NaiveDate, ColumnType::Date); 24 | impl_to_col_type!(bigdecimal::BigDecimal, ColumnType::Decimal); 25 | impl_to_col_type!(f64, ColumnType::Double); 26 | impl_to_col_type!(CqlDuration, ColumnType::Duration); 27 | impl_to_col_type!(f32, ColumnType::Float); 28 | impl_to_col_type!(i32, ColumnType::Int); 29 | impl_to_col_type!(i64, ColumnType::BigInt); 30 | impl_to_col_type!(String, ColumnType::Text); 31 | impl_to_col_type!(Timestamp, ColumnType::Timestamp); 32 | impl_to_col_type!(std::net::IpAddr, ColumnType::Inet); 33 | impl_to_col_type!(i16, ColumnType::SmallInt); 34 | impl_to_col_type!(i8, ColumnType::TinyInt); 35 | impl_to_col_type!(Time, ColumnType::Time); 36 | impl_to_col_type!(uuid::Uuid, ColumnType::Uuid); 37 | impl_to_col_type!(num_bigint::BigInt, ColumnType::Varint); 38 | 39 | impl ToColumnType for Vec { 40 | fn to_column_type() -> ColumnType { 41 | ColumnType::List(Box::new(T::to_column_type())) 42 | } 43 | } 44 | 45 | impl ToColumnType for BTreeMap { 46 | fn to_column_type() -> ColumnType { 47 | ColumnType::Map(Box::new(K::to_column_type()), Box::new(V::to_column_type())) 48 | } 49 | } 50 | 51 | impl ToColumnType for HashMap { 52 | fn to_column_type() -> ColumnType { 53 | ColumnType::Map(Box::new(K::to_column_type()), Box::new(V::to_column_type())) 54 | } 55 | } 56 | 57 | impl ToColumnType for BTreeSet { 58 | fn to_column_type() -> ColumnType { 59 | ColumnType::Set(Box::new(T::to_column_type())) 60 | } 61 | } 62 | 63 | impl ToColumnType for HashSet { 64 | fn to_column_type() -> ColumnType { 65 | ColumnType::Set(Box::new(T::to_column_type())) 66 | } 67 | } 68 | 69 | macro_rules! tuple_impls { 70 | ( $( $types:ident )* ) => { 71 | impl<$($types: ToColumnType),*> ToColumnType for ($($types,)*) { 72 | fn to_column_type() -> ColumnType { 73 | ColumnType::Tuple(vec![$($types::to_column_type()),*]) 74 | } 75 | } 76 | }; 77 | } 78 | 79 | tuple_impls! { A } 80 | tuple_impls! { A B } 81 | tuple_impls! { A B C } 82 | tuple_impls! { A B C D } 83 | tuple_impls! { A B C D E } 84 | tuple_impls! { A B C D E F } 85 | tuple_impls! { A B C D E F G } 86 | tuple_impls! { A B C D E F G H } 87 | tuple_impls! { A B C D E F G H I } 88 | tuple_impls! { A B C D E F G H I J } 89 | tuple_impls! { A B C D E F G H I J K } 90 | tuple_impls! { A B C D E F G H I J K L } 91 | 92 | impl ToColumnType for Option { 93 | fn to_column_type() -> ColumnType { 94 | T::to_column_type() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /scylla-udf-macros/src/export_udf.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::TokenStream as TokenStream2; 3 | use quote::{format_ident, quote}; 4 | use syn::spanned::Spanned; 5 | use syn::{parse_macro_input, FnArg, ItemFn}; 6 | 7 | fn get_parameters_and_arguments( 8 | item: &ItemFn, 9 | path: &TokenStream2, 10 | ) -> Result<(Vec, Vec), TokenStream2> { 11 | let inputs = &item.sig.inputs; 12 | let mut parameters = Vec::with_capacity(inputs.len()); 13 | let mut arguments = Vec::with_capacity(inputs.len()); 14 | for (idx, i) in inputs.iter().enumerate() { 15 | if let FnArg::Typed(pat) = i { 16 | let ident = format_ident!("arg_{}", idx); 17 | let typ = &pat.ty; 18 | parameters.push(quote! { #ident: <#typ as ::#path::WasmConvertible>::WasmType }); 19 | arguments.push(quote! { <#typ as ::#path::WasmConvertible>::from_wasm(#ident) }); 20 | } else { 21 | return Err(syn::Error::new( 22 | i.span(), 23 | "unexpected untyped self parameter in a scylla_udf function.", 24 | ) 25 | .to_compile_error()); 26 | } 27 | } 28 | Ok((parameters, arguments)) 29 | } 30 | 31 | fn get_output_type_and_block( 32 | item: &ItemFn, 33 | arguments: &[TokenStream2], 34 | path: &TokenStream2, 35 | ) -> Result<(TokenStream2, TokenStream2), TokenStream2> { 36 | let fun_name = item.sig.ident.clone(); 37 | if let syn::ReturnType::Type(_, typ) = &item.sig.output { 38 | Ok(( 39 | quote! { -> <#typ as ::#path::WasmConvertible>::WasmType }, 40 | quote! { { 41 | <#typ as ::#path::WasmConvertible>::to_wasm(&#fun_name(#(#arguments),*)) 42 | } }, 43 | )) 44 | } else { 45 | Err(syn::Error::new( 46 | item.sig.output.span(), 47 | "scylla_udf function should return a value.", 48 | ) 49 | .to_compile_error()) 50 | } 51 | } 52 | 53 | fn get_exported_fun( 54 | item: &ItemFn, 55 | parameters: &[TokenStream2], 56 | output_type_token: TokenStream2, 57 | exported_block: TokenStream2, 58 | ) -> TokenStream2 { 59 | let fun_name = &item.sig.ident; 60 | let exported_fun_ident = format_ident!("{}{}", "_scylla_internal_", fun_name); 61 | // The exported function doesn't need to be pub, because it will be included in the final 62 | // binary anyway due to the #[export_name] attribute. No pub helps with the UDT implementation. 63 | let sig_exported = quote! { 64 | extern "C" fn #exported_fun_ident(#(#parameters),*) #output_type_token 65 | }; 66 | let fun_name_string = fun_name.to_string(); 67 | let export_name = quote! { 68 | #[export_name = #fun_name_string] 69 | }; 70 | quote! { 71 | #export_name 72 | #sig_exported #exported_block 73 | } 74 | } 75 | 76 | /// The macro transforms a function: 77 | /// ```ignore 78 | /// #[scylla_udf::export_udf] 79 | /// fn foo(arg1: u32, arg2: String) -> u32 { 80 | /// arg1 + arg2.len() as u32 81 | /// } 82 | /// ``` 83 | /// into something like: 84 | /// ```ignore 85 | /// fn foo(arg1: u32, arg2: String) -> u32 { 86 | /// arg1 + arg2.len() as u32 87 | /// } 88 | /// #[export_name = "foo"] 89 | /// extern "C" fn _scylla_internal_foo(arg1: u32, arg2: WasmPtr) -> u32 { 90 | /// foo(from_wasm(arg1), from_wasm(arg2)).to_wasm() 91 | /// } 92 | /// ``` 93 | pub(crate) fn export_udf(attrs: TokenStream, input: TokenStream) -> TokenStream { 94 | let item = parse_macro_input!(input as ItemFn); 95 | let atrs = syn::parse_macro_input!(attrs as syn::AttributeArgs); 96 | let path = crate::path::get_path(&atrs).expect("Couldn't get path to the scylla_udf crate"); 97 | let (parameters, arguments) = match get_parameters_and_arguments(&item, &path) { 98 | Ok(pa) => pa, 99 | Err(e) => return e.into(), 100 | }; 101 | let (output_type_token, exported_block) = 102 | match get_output_type_and_block(&item, &arguments, &path) { 103 | Ok(oe) => oe, 104 | Err(e) => return e.into(), 105 | }; 106 | let exported_fun = get_exported_fun(&item, ¶meters, output_type_token, exported_block); 107 | quote! { 108 | #item 109 | #exported_fun 110 | } 111 | .into() 112 | } 113 | -------------------------------------------------------------------------------- /scylla-udf-macros/src/export_newtype.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::TokenStream as TokenStream2; 3 | use quote::quote; 4 | use syn::Fields; 5 | 6 | struct NewtypeStruct { 7 | struct_name: syn::Ident, 8 | field_type: syn::Type, 9 | generics: syn::Generics, 10 | } 11 | 12 | fn get_newtype_struct(st: &syn::ItemStruct) -> Result { 13 | let struct_name = &st.ident; 14 | let struct_fields = match &st.fields { 15 | Fields::Unnamed(named_fields) => named_fields, 16 | _ => { 17 | return Err(syn::Error::new_spanned( 18 | st, 19 | "#[scylla_udf::export_newtype] error: struct has named fields.", 20 | ) 21 | .to_compile_error()); 22 | } 23 | }; 24 | if struct_fields.unnamed.len() > 1 { 25 | return Err(syn::Error::new_spanned( 26 | st, 27 | "#[scylla_udf::export_newtype] error: struct has more than 1 field.", 28 | ) 29 | .to_compile_error()); 30 | } 31 | let field_type = match struct_fields.unnamed.first() { 32 | Some(field) => &field.ty, 33 | None => { 34 | return Err(syn::Error::new_spanned( 35 | st, 36 | "#[scylla_udf::export_newtype] error: struct has no fields.", 37 | ) 38 | .to_compile_error()); 39 | } 40 | }; 41 | 42 | Ok(NewtypeStruct { 43 | struct_name: struct_name.clone(), 44 | field_type: field_type.clone(), 45 | generics: st.generics.clone(), 46 | }) 47 | } 48 | 49 | fn impl_wasm_convertible(nst: &NewtypeStruct, path: &TokenStream2) -> TokenStream2 { 50 | let struct_name = &nst.struct_name; 51 | let struct_type = &nst.field_type; 52 | let (impl_generics, ty_generics, where_clause) = nst.generics.split_for_impl(); 53 | quote! { 54 | impl #impl_generics ::#path::WasmConvertible for #struct_name #ty_generics #where_clause { 55 | type WasmType = <#struct_type as ::#path::WasmConvertible>::WasmType; 56 | fn from_wasm(arg: Self::WasmType) -> Self { 57 | #struct_name(<#struct_type as ::#path::WasmConvertible>::from_wasm(arg)) 58 | } 59 | fn to_wasm(&self) -> Self::WasmType { 60 | <#struct_type as ::#path::WasmConvertible>::to_wasm(&self.0) 61 | } 62 | } 63 | } 64 | } 65 | 66 | fn impl_to_col_type(nst: &NewtypeStruct, path: &TokenStream2) -> TokenStream2 { 67 | let struct_name = &nst.struct_name; 68 | let struct_type = &nst.field_type; 69 | let (impl_generics, ty_generics, where_clause) = nst.generics.split_for_impl(); 70 | quote! { 71 | impl #impl_generics ::#path::ToColumnType for #struct_name #ty_generics #where_clause { 72 | fn to_column_type() -> ::#path::ColumnType { 73 | <#struct_type as ::#path::ToColumnType>::to_column_type() 74 | } 75 | } 76 | } 77 | } 78 | 79 | fn impl_value(nst: &NewtypeStruct, path: &TokenStream2) -> TokenStream2 { 80 | let struct_name = &nst.struct_name; 81 | let struct_type = &nst.field_type; 82 | let (impl_generics, ty_generics, where_clause) = nst.generics.split_for_impl(); 83 | 84 | quote! { 85 | impl #impl_generics ::#path::Value for #struct_name #ty_generics #where_clause { 86 | fn serialize(&self, buf: &mut ::std::vec::Vec<::core::primitive::u8>) -> ::std::result::Result<(), ::#path::ValueTooBig> { 87 | <#struct_type as ::#path::Value>::serialize(&self.0, buf) 88 | } 89 | } 90 | } 91 | } 92 | 93 | fn impl_from_cql_val(nst: &NewtypeStruct, path: &TokenStream2) -> TokenStream2 { 94 | let struct_name = &nst.struct_name; 95 | let struct_type = &nst.field_type; 96 | let (impl_generics, ty_generics, where_clause) = nst.generics.split_for_impl(); 97 | 98 | quote! { 99 | impl #impl_generics ::#path::FromCqlVal<::#path::CqlValue> for #struct_name #ty_generics #where_clause { 100 | fn from_cql(val: ::#path::CqlValue) -> ::std::result::Result { 101 | <#struct_type as ::#path::FromCqlVal<::#path::CqlValue>>::from_cql(val).map(|v| #struct_name(v)) 102 | } 103 | } 104 | } 105 | } 106 | 107 | pub(crate) fn export_newtype(attrs: TokenStream, item: TokenStream) -> TokenStream { 108 | let st = syn::parse_macro_input!(item as syn::ItemStruct); 109 | let atrs = syn::parse_macro_input!(attrs as syn::AttributeArgs); 110 | let path = crate::path::get_path(&atrs).expect("Couldn't get path to the scylla_udf crate"); 111 | let newtype_struct = match get_newtype_struct(&st) { 112 | Ok(nst) => nst, 113 | Err(e) => return e.into(), 114 | }; 115 | let wasm_convertible = impl_wasm_convertible(&newtype_struct, &path); 116 | let to_col_type = impl_to_col_type(&newtype_struct, &path); 117 | let value = impl_value(&newtype_struct, &path); 118 | let from_cql_val = impl_from_cql_val(&newtype_struct, &path); 119 | quote! { 120 | #st 121 | #wasm_convertible 122 | #to_col_type 123 | #value 124 | #from_cql_val 125 | } 126 | .into() 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust helper library for Scylla UDFs 2 | 3 | This crate allows writing pure Rust functions that can be used as Scylla UDFs. 4 | 5 | **Note: this crate is officially supported and ready to use. However, UDFs are still an experimental feature in ScyllaDB, and the crate has not been widely used, which is why it's still in beta and its API is subject to change. We appreciate bug reports and pull requests!** 6 | 7 | ## Usage 8 | 9 | > [!NOTE] 10 | > In Rust versions 1.77 and below, the WASI target was called `wasm32-wasi` instead of `wasm32-wasip1`. 11 | > 12 | > For more information, see https://blog.rust-lang.org/2024/04/09/updates-to-rusts-wasi-targets.html. 13 | 14 | ### Prerequisites 15 | 16 | To use this helper library in Scylla you'll need: 17 | * `cargo` 18 | * Generating Wasm is also possible with just the rustc compiler, but the guide will assume that `cargo` is installed 19 | * Standard library for Rust `wasm32-wasip1` 20 | * Can be added in rustup installations using `rustup target add wasm32-wasip1` 21 | * For non rustup setups, you can try following the steps at https://rustwasm.github.io/docs/wasm-pack/prerequisites/non-rustup-setups.html 22 | * Also available as an rpm: `rust-std-static-wasm32-wasip1` 23 | * `wasm2wat` parser 24 | * Available in many distributions in the `wabt` package, which also provides the `wasm-strip` tool 25 | * (Optionally) `wasm-opt` tool for optimizing the Wasm binary 26 | * Available in many distributions in the `binaryen` package 27 | 28 | ### Compilation 29 | 30 | We recommend a setup with cargo. 31 | 32 | 1. Start with a library package: 33 | ``` 34 | cargo new --lib my_udf_library 35 | ``` 36 | 2. Add the scylla-udf dependency: 37 | ``` 38 | cargo add scylla-udf 39 | ``` 40 | 3. Add the following lines to the Cargo.toml to set the crate-type to cdylib: 41 | ``` 42 | [lib] 43 | crate-type = ["cdylib"] 44 | ``` 45 | 4. Implement your package, exporting Scylla UDFs using the `scylla_udf::export_udf` macro. 46 | 5. Build the package using the wasm32-wasip1 target: 47 | ``` 48 | RUSTFLAGS="-C link-args=-zstack-size=131072" cargo build --target=wasm32-wasip1 49 | ``` 50 | > **_NOTE:_** The default size of the stack in WASI (1MB) causes warnings about oversized allocations in Scylla, so we recommend setting the stack size to a lower value. This is done using the `RUSTFLAGS` environmental variable in the command above for a new size of 128KB, which should be enough for most use cases. 51 | 52 | 6. Find the compiled `.wasm` binary. Let's assume it's `target/wasm32-wasip1/debug/my_udf_library.wasm`. 53 | 7. (optional) Optimize the binary using `wasm-opt -O3 target/wasm32-wasip1/debug/my_udf_library.wasm -o target/wasm32-wasip1/debug/my_udf_library.wasm` (can be combined with using `cargo build --release` profile) 54 | 8. (optional) Reduce the size of the binary using `wasm-strip target/wasm32-wasip1/debug/my_udf_library.wasm` 55 | 9. Translate the binary into `wat`: 56 | ``` 57 | wasm2wat target/wasm32-wasip1/debug/my_udf_library.wasm > target/wasm32-wasip1/debug/my_udf_library.wat 58 | ``` 59 | 60 | ### CQL Statement 61 | 62 | The resulting `target/wasm32-wasi/debug/my_udf_library.wat` code can now be used directly in a `CREATE FUNCTION` statement. The resulting code will most likely 63 | contain `'` characters, so it may be necessary to first replace them with `''`, so that they're usable in a CQL string. 64 | 65 | For example, if you have an [Rust UDF](examples/commas.rs) that joins a list of words using commas, you can create a Scylla UDF using the following statement: 66 | ``` 67 | CREATE FUNCTION commas(string list) CALLED ON NULL INPUT RETURNS text LANGUAGE wasm AS ' (module ...) ' 68 | ``` 69 | 70 | > **_NOTE:_** The LANGUAGE used for Wasm UDFs is `xwasm` instead of `wasm` in Scylla versions 5.1 and 5.2. 71 | 72 | ## CQL Type Mapping 73 | 74 | The argument and return value types used in functions annotated with `#[export_udf]` must all map to CQL types used in the `CREATE FUNCTION` statements used in Scylla, according to the tables below. 75 | 76 | If the Scylla function is created with types that do not match the types used in the Rust function, calling the UDF will fail or produce arbitrary results. 77 | 78 | ### Native types 79 | 80 | | CQL Type | Rust type | 81 | | --------- | ----------------------------- | 82 | | ASCII | String | 83 | | BIGINT | i64 | 84 | | BLOB | Vec\ | 85 | | BOOLEAN | bool | 86 | | COUNTER | scylla_udf::Counter | 87 | | DATE | chrono::NaiveDate | 88 | | DECIMAL | bigdecimal::Decimal | 89 | | DOUBLE | f64 | 90 | | DURATION | scylla_udf::CqlDuration | 91 | | FLOAT | f32 | 92 | | INET | std::net::IpAddr | 93 | | INT | i32 | 94 | | SMALLINT | i16 | 95 | | TEXT | String | 96 | | TIME | scylla_udf::Time | 97 | | TIMESTAMP | scylla_udf::Timestamp | 98 | | TIMEUUID | uuid::Uuid | 99 | | TINYINT | i8 | 100 | | UUID | uuid::Uuid | 101 | | VARCHAR | String | 102 | | VARINT | num_bigint::BigInt | 103 | 104 | ### Collections 105 | 106 | If a CQL type `T` maps to Rust type `RustT`, you can use it as a collection parameter: 107 | 108 | | CQL Type | Rust type | 109 | | ---------- | ------------------------------------------------------------------------------------- | 110 | | LIST\ | Vec\ | 111 | | MAP\ | std::collections::BTreeMap\, std::collections::HashMap\ | 112 | | SET\ | Vec\, std::collections::BTreeSet\, std::collections::HashSet\ | 113 | 114 | 115 | ### Tuples 116 | 117 | If CQL types `T1`, `T2`, ... map to Rust types `RustT1`, `RustT2`, ..., you can use them in tuples: 118 | 119 | | CQL Type | Rust type | 120 | | -------- | ---------------------------------- | 121 | | TUPLE\ | (RustT1, RustT2, ...) | 122 | 123 | ### Nulls 124 | 125 | If a CQL Value of type T that's mapped to type RustT may be a null (all parameter and return types in `CALLED ON NULL INPUT` UDFs), then the type used in the Rust function should be Option\. 126 | 127 | ## Contributing 128 | 129 | In general, try to follow the same rules as in https://github.com/scylladb/scylla-rust-driver/blob/main/CONTRIBUTING.md 130 | 131 | ### Testing 132 | 133 | This crate is meant to be compiled to a `wasm32-wasip1` target and ran in a WASM runtime. The tests that use WASM-specific code will most likely not succeed when executed in a different way (in particular, with a simple `cargo test` command). 134 | 135 | For example, if you have the [wasmtime](https://docs.wasmtime.dev/cli-install.html) runtime installed and in `PATH`, you can use the following command to run tests: 136 | ```text 137 | CARGO_TARGET_WASM32_WASI_RUNNER="wasmtime --allow-unknown-exports" cargo test --target=wasm32-wasip1 138 | ``` 139 | -------------------------------------------------------------------------------- /scylla-udf/src/wasm_convertible.rs: -------------------------------------------------------------------------------- 1 | use crate::from_wasmptr::FromWasmPtr; 2 | use crate::to_wasmptr::ToWasmPtr; 3 | use crate::wasmptr::WasmPtr; 4 | use scylla_cql::frame::value::{Counter, CqlDuration, Time, Timestamp}; 5 | use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; 6 | use std::convert::TryFrom; 7 | 8 | pub trait WasmConvertible { 9 | type WasmType; 10 | fn from_wasm(arg: Self::WasmType) -> Self; 11 | fn to_wasm(&self) -> Self::WasmType; 12 | } 13 | 14 | // This macro implements WasmConvertible given a Rust type and the resulting WasmType 15 | macro_rules! impl_wasm_convertible_native { 16 | ($rust_type:ty) => { 17 | impl WasmConvertible for $rust_type { 18 | type WasmType = $rust_type; 19 | fn from_wasm(arg: Self::WasmType) -> Self { 20 | arg 21 | } 22 | fn to_wasm(&self) -> Self::WasmType { 23 | *self 24 | } 25 | } 26 | }; 27 | } 28 | 29 | impl_wasm_convertible_native!(i32); 30 | impl_wasm_convertible_native!(i64); 31 | impl_wasm_convertible_native!(f32); 32 | impl_wasm_convertible_native!(f64); 33 | 34 | // This macro implements WasmConvertible given a Rust type that can be converted to the given WasmType using TryFrom 35 | macro_rules! impl_wasm_convertible_scalar { 36 | ($rust_type:ty, $scalar_type:ty) => { 37 | impl WasmConvertible for $rust_type { 38 | type WasmType = $scalar_type; 39 | fn from_wasm(arg: Self::WasmType) -> Self { 40 | <$rust_type>::try_from(arg) 41 | .expect("Failed to convert from wasm type to a rust type") 42 | } 43 | fn to_wasm(&self) -> Self::WasmType { 44 | *self as Self::WasmType 45 | } 46 | } 47 | }; 48 | } 49 | 50 | impl_wasm_convertible_scalar!(i8, i32); 51 | impl_wasm_convertible_scalar!(i16, i32); 52 | 53 | // Can't convert bool to i32 using TryFrom, so we need a special implementation 54 | impl WasmConvertible for bool { 55 | type WasmType = i32; 56 | fn from_wasm(arg: Self::WasmType) -> Self { 57 | arg != 0 58 | } 59 | fn to_wasm(&self) -> Self::WasmType { 60 | i32::from(*self) 61 | } 62 | } 63 | 64 | // This macro implements WasmConvertible given a Rust type that can be (de)serialized using FromWasmPtr and ToWasmPtr 65 | macro_rules! impl_wasm_convertible_serialized { 66 | ($rust_type:ty) => { 67 | impl WasmConvertible for $rust_type { 68 | type WasmType = WasmPtr; 69 | fn from_wasm(arg: Self::WasmType) -> Self { 70 | ::from_wasmptr(arg) 71 | } 72 | fn to_wasm(&self) -> Self::WasmType { 73 | ::to_wasmptr(self) 74 | } 75 | } 76 | }; 77 | } 78 | 79 | impl_wasm_convertible_serialized!(Counter); 80 | impl_wasm_convertible_serialized!(chrono::NaiveDate); 81 | impl_wasm_convertible_serialized!(bigdecimal::BigDecimal); 82 | impl_wasm_convertible_serialized!(CqlDuration); 83 | impl_wasm_convertible_serialized!(String); 84 | impl_wasm_convertible_serialized!(Timestamp); 85 | impl_wasm_convertible_serialized!(std::net::IpAddr); 86 | impl_wasm_convertible_serialized!(Time); 87 | impl_wasm_convertible_serialized!(uuid::Uuid); 88 | impl_wasm_convertible_serialized!(num_bigint::BigInt); 89 | 90 | // This macro implements WasmConvertible given a Rust type with a generic parameter T that can be (de)serialized using FromWasmPtr and ToWasmPtr 91 | macro_rules! impl_wasm_convertible_serialized_generic { 92 | ($rust_type:ty) => { 93 | impl WasmConvertible for $rust_type 94 | where 95 | $rust_type: FromWasmPtr + ToWasmPtr, 96 | { 97 | type WasmType = WasmPtr; 98 | fn from_wasm(arg: Self::WasmType) -> Self { 99 | ::from_wasmptr(arg) 100 | } 101 | fn to_wasm(&self) -> Self::WasmType { 102 | ::to_wasmptr(self) 103 | } 104 | } 105 | }; 106 | } 107 | 108 | impl_wasm_convertible_serialized_generic!(Option); 109 | // Implements both lists and blobs 110 | impl_wasm_convertible_serialized_generic!(Vec); 111 | impl_wasm_convertible_serialized_generic!(BTreeSet); 112 | impl_wasm_convertible_serialized_generic!(HashSet); 113 | 114 | // This macro implements WasmConvertible given a Rust type with generic parameters K and V that can be (de)serialized using FromWasmPtr and ToWasmPtr 115 | macro_rules! impl_wasm_convertible_serialized_double_generic { 116 | ($rust_type:ty) => { 117 | impl WasmConvertible for $rust_type 118 | where 119 | $rust_type: FromWasmPtr + ToWasmPtr, 120 | { 121 | type WasmType = WasmPtr; 122 | fn from_wasm(arg: Self::WasmType) -> Self { 123 | ::from_wasmptr(arg) 124 | } 125 | fn to_wasm(&self) -> Self::WasmType { 126 | ::to_wasmptr(self) 127 | } 128 | } 129 | }; 130 | } 131 | 132 | impl_wasm_convertible_serialized_double_generic!(BTreeMap); 133 | impl_wasm_convertible_serialized_double_generic!(HashMap); 134 | 135 | // This macro implements WasmConvertible for tuples of types that can be (de)serialized using FromWasmPtr and ToWasmPtr 136 | macro_rules! impl_wasm_convertible_serialized_tuple { 137 | ( $( $types:ident )* ) => { 138 | impl<$($types),*> WasmConvertible for ($($types,)*) 139 | where 140 | ($($types,)*): FromWasmPtr + ToWasmPtr 141 | { 142 | type WasmType = WasmPtr; 143 | fn from_wasm(arg: Self::WasmType) -> Self { 144 | ::from_wasmptr(arg) 145 | } 146 | fn to_wasm(&self) -> Self::WasmType { 147 | ::to_wasmptr(self) 148 | } 149 | } 150 | }; 151 | } 152 | 153 | impl_wasm_convertible_serialized_tuple! { A } 154 | impl_wasm_convertible_serialized_tuple! { A B } 155 | impl_wasm_convertible_serialized_tuple! { A B C } 156 | impl_wasm_convertible_serialized_tuple! { A B C D } 157 | impl_wasm_convertible_serialized_tuple! { A B C D E } 158 | impl_wasm_convertible_serialized_tuple! { A B C D E F } 159 | impl_wasm_convertible_serialized_tuple! { A B C D E F G } 160 | impl_wasm_convertible_serialized_tuple! { A B C D E F G H } 161 | impl_wasm_convertible_serialized_tuple! { A B C D E F G H I } 162 | impl_wasm_convertible_serialized_tuple! { A B C D E F G H I J } 163 | impl_wasm_convertible_serialized_tuple! { A B C D E F G H I J K } 164 | impl_wasm_convertible_serialized_tuple! { A B C D E F G H I J K L } 165 | 166 | #[cfg(test)] 167 | mod tests { 168 | use super::WasmConvertible; 169 | use crate::*; 170 | 171 | #[test] 172 | fn i32_convert() { 173 | assert_eq!(i32::from_wasm(42_i32.to_wasm()), 42_i32); 174 | assert_eq!(i32::from_wasm(-42_i32), -42_i32); 175 | assert_eq!((-42_i32).to_wasm(), -42_i32); 176 | } 177 | #[test] 178 | fn i64_convert() { 179 | assert_eq!(i64::from_wasm(42_i64.to_wasm()), 42_i64); 180 | assert_eq!(i64::from_wasm(-42_i64), -42_i64); 181 | assert_eq!((-42_i64).to_wasm(), -42_i64); 182 | } 183 | #[test] 184 | fn f32_convert() { 185 | assert_eq!(f32::from_wasm(0.42_f32.to_wasm()), 0.42_f32); 186 | assert_eq!(f32::from_wasm(-0.42_f32), -0.42_f32); 187 | assert_eq!((-0.42_f32).to_wasm(), -0.42_f32); 188 | } 189 | #[test] 190 | fn f64_convert() { 191 | assert_eq!(f64::from_wasm(0.42_f64.to_wasm()), 0.42_f64); 192 | assert_eq!(f64::from_wasm(-0.42_f64), -0.42_f64); 193 | assert_eq!((-0.42_f64).to_wasm(), -0.42_f64); 194 | } 195 | #[test] 196 | fn i8_convert() { 197 | assert_eq!(i8::from_wasm(42_i8.to_wasm()), 42_i8); 198 | assert_eq!(i8::from_wasm(-42_i32), -42_i8); 199 | assert_eq!((-42_i8).to_wasm(), -42_i32); 200 | } 201 | #[test] 202 | fn i16_convert() { 203 | assert_eq!(i64::from_wasm(42_i64.to_wasm()), 42_i64); 204 | assert_eq!(i64::from_wasm(-42_i64), -42_i64); 205 | assert_eq!((-42_i64).to_wasm(), -42_i64); 206 | } 207 | #[test] 208 | fn bool_convert() { 209 | assert!(bool::from_wasm(true.to_wasm())); 210 | assert!(bool::from_wasm(1_i32)); 211 | assert_eq!(bool::to_wasm(&false), 0_i32); 212 | } 213 | #[test] 214 | fn blob_convert() { 215 | let blob: Vec = vec![1, 2, 3, 4, 5]; 216 | assert_eq!(Vec::::from_wasm(blob.to_wasm()), blob); 217 | } 218 | #[test] 219 | fn counter_convert() { 220 | assert_eq!(Counter::from_wasm(Counter(13).to_wasm()), Counter(13)); 221 | } 222 | #[test] 223 | fn naive_date_convert() { 224 | use chrono::NaiveDate; 225 | let date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(); 226 | assert_eq!(NaiveDate::from_wasm(date.to_wasm()), date); 227 | } 228 | #[test] 229 | fn big_decimal_convert() { 230 | use bigdecimal::BigDecimal; 231 | use std::str::FromStr; 232 | let bigd = BigDecimal::from_str("547318970434573134570").unwrap(); 233 | assert_eq!(BigDecimal::from_wasm(bigd.to_wasm()), bigd); 234 | } 235 | #[test] 236 | fn cql_duration_convert() { 237 | let dur = CqlDuration { 238 | months: 1, 239 | days: 2, 240 | nanoseconds: 3, 241 | }; 242 | assert_eq!(CqlDuration::from_wasm(dur.to_wasm()), dur); 243 | } 244 | #[test] 245 | fn timestamp_convert() { 246 | use chrono::Duration; 247 | let ts = Timestamp(Duration::weeks(2)); 248 | assert_eq!(Timestamp::from_wasm(ts.to_wasm()), ts); 249 | } 250 | #[test] 251 | fn string_convert() { 252 | let s = String::from("abc"); 253 | assert_eq!(String::from_wasm(s.to_wasm()), s); 254 | } 255 | #[test] 256 | fn inet_convert() { 257 | use std::net::IpAddr; 258 | let ip = IpAddr::from([127, 0, 0, 1]); 259 | assert_eq!(IpAddr::from_wasm(ip.to_wasm()), ip); 260 | } 261 | #[test] 262 | fn time_convert() { 263 | use chrono::Duration; 264 | let t = Time(Duration::hours(3)); 265 | assert_eq!(Time::from_wasm(t.to_wasm()), t); 266 | } 267 | #[test] 268 | fn uuid_convert() { 269 | use uuid::Uuid; 270 | let uuid = Uuid::NAMESPACE_OID; 271 | assert_eq!(Uuid::from_wasm(uuid.to_wasm()), uuid); 272 | } 273 | #[test] 274 | fn big_int_convert() { 275 | use num_bigint::BigInt; 276 | use std::str::FromStr; 277 | let bi = BigInt::from_str("420000000000000000").unwrap(); 278 | assert_eq!(BigInt::from_wasm(bi.to_wasm()), bi); 279 | } 280 | 281 | #[test] 282 | fn vec_convert() { 283 | // convert vec of strings 284 | let vec = vec![String::from("a"), String::from("b")]; 285 | assert_eq!(Vec::::from_wasm(vec.to_wasm()), vec); 286 | } 287 | #[test] 288 | fn hashset_convert() { 289 | use std::collections::HashSet; 290 | let mut set = HashSet::new(); 291 | set.insert(String::from("a")); 292 | set.insert(String::from("b")); 293 | assert_eq!(HashSet::::from_wasm(set.to_wasm()), set); 294 | } 295 | #[test] 296 | fn btreeset_convert() { 297 | use std::collections::BTreeSet; 298 | let mut set = BTreeSet::new(); 299 | set.insert((1, String::from("a"))); 300 | set.insert((3, String::from("b"))); 301 | assert_eq!(BTreeSet::<(i32, String)>::from_wasm(set.to_wasm()), set); 302 | } 303 | #[test] 304 | fn hashmap_convert() { 305 | use std::collections::HashMap; 306 | let mut map = HashMap::new(); 307 | map.insert(String::from("a"), 5_i16); 308 | map.insert(String::from("b"), 55_i16); 309 | assert_eq!(HashMap::::from_wasm(map.to_wasm()), map); 310 | } 311 | #[test] 312 | fn btreemap_convert() { 313 | use std::collections::BTreeMap; 314 | let mut map = BTreeMap::new(); 315 | map.insert((1, 2), String::from("a")); 316 | map.insert((3, 4), String::from("b")); 317 | assert_eq!( 318 | BTreeMap::<(i32, i32), String>::from_wasm(map.to_wasm()), 319 | map 320 | ); 321 | } 322 | #[test] 323 | fn tuple_convert() { 324 | let tup = (String::from("a"), 5_i8); 325 | assert_eq!(<(String, i8)>::from_wasm(tup.to_wasm()), tup); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /Cargo.lock.msrv: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "android-tzdata" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 25 | 26 | [[package]] 27 | name = "android_system_properties" 28 | version = "0.1.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 31 | dependencies = [ 32 | "libc", 33 | ] 34 | 35 | [[package]] 36 | name = "async-trait" 37 | version = "0.1.83" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" 40 | dependencies = [ 41 | "proc-macro2", 42 | "quote", 43 | "syn 2.0.85", 44 | ] 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.4.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 51 | 52 | [[package]] 53 | name = "backtrace" 54 | version = "0.3.74" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 57 | dependencies = [ 58 | "addr2line", 59 | "cfg-if", 60 | "libc", 61 | "miniz_oxide", 62 | "object", 63 | "rustc-demangle", 64 | "windows-targets", 65 | ] 66 | 67 | [[package]] 68 | name = "bigdecimal" 69 | version = "0.2.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "d1e50562e37200edf7c6c43e54a08e64a5553bfb59d9c297d5572512aa517256" 72 | dependencies = [ 73 | "num-bigint", 74 | "num-integer", 75 | "num-traits", 76 | ] 77 | 78 | [[package]] 79 | name = "bumpalo" 80 | version = "3.16.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 83 | 84 | [[package]] 85 | name = "byteorder" 86 | version = "1.5.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 89 | 90 | [[package]] 91 | name = "bytes" 92 | version = "1.8.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" 95 | 96 | [[package]] 97 | name = "cc" 98 | version = "1.1.31" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" 101 | dependencies = [ 102 | "shlex", 103 | ] 104 | 105 | [[package]] 106 | name = "cfg-if" 107 | version = "1.0.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 110 | 111 | [[package]] 112 | name = "chrono" 113 | version = "0.4.38" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 116 | dependencies = [ 117 | "android-tzdata", 118 | "iana-time-zone", 119 | "js-sys", 120 | "num-traits", 121 | "wasm-bindgen", 122 | "windows-targets", 123 | ] 124 | 125 | [[package]] 126 | name = "core-foundation-sys" 127 | version = "0.8.7" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 130 | 131 | [[package]] 132 | name = "equivalent" 133 | version = "1.0.1" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 136 | 137 | [[package]] 138 | name = "examples" 139 | version = "0.0.0" 140 | dependencies = [ 141 | "bigdecimal", 142 | "chrono", 143 | "num-bigint", 144 | "scylla-udf", 145 | "uuid", 146 | ] 147 | 148 | [[package]] 149 | name = "gimli" 150 | version = "0.31.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 153 | 154 | [[package]] 155 | name = "hashbrown" 156 | version = "0.15.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 159 | 160 | [[package]] 161 | name = "iana-time-zone" 162 | version = "0.1.61" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 165 | dependencies = [ 166 | "android_system_properties", 167 | "core-foundation-sys", 168 | "iana-time-zone-haiku", 169 | "js-sys", 170 | "wasm-bindgen", 171 | "windows-core", 172 | ] 173 | 174 | [[package]] 175 | name = "iana-time-zone-haiku" 176 | version = "0.1.2" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 179 | dependencies = [ 180 | "cc", 181 | ] 182 | 183 | [[package]] 184 | name = "indexmap" 185 | version = "2.6.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 188 | dependencies = [ 189 | "equivalent", 190 | "hashbrown", 191 | ] 192 | 193 | [[package]] 194 | name = "js-sys" 195 | version = "0.3.72" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 198 | dependencies = [ 199 | "wasm-bindgen", 200 | ] 201 | 202 | [[package]] 203 | name = "libc" 204 | version = "0.2.161" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 207 | 208 | [[package]] 209 | name = "log" 210 | version = "0.4.22" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 213 | 214 | [[package]] 215 | name = "lz4_flex" 216 | version = "0.9.5" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "1a8cbbb2831780bc3b9c15a41f5b49222ef756b6730a95f3decfdd15903eb5a3" 219 | dependencies = [ 220 | "twox-hash", 221 | ] 222 | 223 | [[package]] 224 | name = "memchr" 225 | version = "2.7.4" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 228 | 229 | [[package]] 230 | name = "miniz_oxide" 231 | version = "0.8.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 234 | dependencies = [ 235 | "adler2", 236 | ] 237 | 238 | [[package]] 239 | name = "num-bigint" 240 | version = "0.3.3" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" 243 | dependencies = [ 244 | "autocfg", 245 | "num-integer", 246 | "num-traits", 247 | ] 248 | 249 | [[package]] 250 | name = "num-integer" 251 | version = "0.1.46" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 254 | dependencies = [ 255 | "num-traits", 256 | ] 257 | 258 | [[package]] 259 | name = "num-traits" 260 | version = "0.2.19" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 263 | dependencies = [ 264 | "autocfg", 265 | ] 266 | 267 | [[package]] 268 | name = "num_enum" 269 | version = "0.5.11" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" 272 | dependencies = [ 273 | "num_enum_derive", 274 | ] 275 | 276 | [[package]] 277 | name = "num_enum_derive" 278 | version = "0.5.11" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" 281 | dependencies = [ 282 | "proc-macro-crate", 283 | "proc-macro2", 284 | "quote", 285 | "syn 1.0.109", 286 | ] 287 | 288 | [[package]] 289 | name = "object" 290 | version = "0.36.5" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 293 | dependencies = [ 294 | "memchr", 295 | ] 296 | 297 | [[package]] 298 | name = "once_cell" 299 | version = "1.20.2" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 302 | 303 | [[package]] 304 | name = "pin-project-lite" 305 | version = "0.2.15" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 308 | 309 | [[package]] 310 | name = "proc-macro-crate" 311 | version = "1.3.1" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" 314 | dependencies = [ 315 | "once_cell", 316 | "toml_edit", 317 | ] 318 | 319 | [[package]] 320 | name = "proc-macro2" 321 | version = "1.0.89" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 324 | dependencies = [ 325 | "unicode-ident", 326 | ] 327 | 328 | [[package]] 329 | name = "quote" 330 | version = "1.0.37" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 333 | dependencies = [ 334 | "proc-macro2", 335 | ] 336 | 337 | [[package]] 338 | name = "rustc-demangle" 339 | version = "0.1.24" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 342 | 343 | [[package]] 344 | name = "scylla-cql" 345 | version = "0.0.4" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "2d6615b8ff55a3bd86c8ad259560a05548f2b73cc0cf610564369dcea7aff91c" 348 | dependencies = [ 349 | "async-trait", 350 | "bigdecimal", 351 | "byteorder", 352 | "bytes", 353 | "chrono", 354 | "lz4_flex", 355 | "num-bigint", 356 | "num_enum", 357 | "scylla-macros", 358 | "snap", 359 | "thiserror", 360 | "tokio", 361 | "uuid", 362 | ] 363 | 364 | [[package]] 365 | name = "scylla-macros" 366 | version = "0.2.3" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "66bbac3874ee838894b5a82c5833c2b0b29d39ca5ad486d982ee434177b2cc57" 369 | dependencies = [ 370 | "proc-macro2", 371 | "quote", 372 | "syn 2.0.85", 373 | ] 374 | 375 | [[package]] 376 | name = "scylla-udf" 377 | version = "0.1.0" 378 | dependencies = [ 379 | "bigdecimal", 380 | "bytes", 381 | "chrono", 382 | "libc", 383 | "num-bigint", 384 | "scylla-cql", 385 | "scylla-udf-macros", 386 | "uuid", 387 | ] 388 | 389 | [[package]] 390 | name = "scylla-udf-macros" 391 | version = "0.1.0" 392 | dependencies = [ 393 | "proc-macro2", 394 | "quote", 395 | "syn 1.0.109", 396 | ] 397 | 398 | [[package]] 399 | name = "shlex" 400 | version = "1.3.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 403 | 404 | [[package]] 405 | name = "snap" 406 | version = "1.1.1" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" 409 | 410 | [[package]] 411 | name = "static_assertions" 412 | version = "1.1.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 415 | 416 | [[package]] 417 | name = "syn" 418 | version = "1.0.109" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 421 | dependencies = [ 422 | "proc-macro2", 423 | "quote", 424 | "unicode-ident", 425 | ] 426 | 427 | [[package]] 428 | name = "syn" 429 | version = "2.0.85" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" 432 | dependencies = [ 433 | "proc-macro2", 434 | "quote", 435 | "unicode-ident", 436 | ] 437 | 438 | [[package]] 439 | name = "tests" 440 | version = "0.0.0" 441 | dependencies = [ 442 | "bigdecimal", 443 | "bytes", 444 | "chrono", 445 | "libc", 446 | "num-bigint", 447 | "scylla-udf", 448 | "uuid", 449 | ] 450 | 451 | [[package]] 452 | name = "thiserror" 453 | version = "1.0.65" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" 456 | dependencies = [ 457 | "thiserror-impl", 458 | ] 459 | 460 | [[package]] 461 | name = "thiserror-impl" 462 | version = "1.0.65" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" 465 | dependencies = [ 466 | "proc-macro2", 467 | "quote", 468 | "syn 2.0.85", 469 | ] 470 | 471 | [[package]] 472 | name = "tokio" 473 | version = "1.38.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" 476 | dependencies = [ 477 | "backtrace", 478 | "bytes", 479 | "pin-project-lite", 480 | ] 481 | 482 | [[package]] 483 | name = "toml_datetime" 484 | version = "0.6.8" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 487 | 488 | [[package]] 489 | name = "toml_edit" 490 | version = "0.19.15" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 493 | dependencies = [ 494 | "indexmap", 495 | "toml_datetime", 496 | "winnow", 497 | ] 498 | 499 | [[package]] 500 | name = "twox-hash" 501 | version = "1.6.3" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" 504 | dependencies = [ 505 | "cfg-if", 506 | "static_assertions", 507 | ] 508 | 509 | [[package]] 510 | name = "unicode-ident" 511 | version = "1.0.13" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 514 | 515 | [[package]] 516 | name = "uuid" 517 | version = "1.11.0" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" 520 | 521 | [[package]] 522 | name = "wasm-bindgen" 523 | version = "0.2.95" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 526 | dependencies = [ 527 | "cfg-if", 528 | "once_cell", 529 | "wasm-bindgen-macro", 530 | ] 531 | 532 | [[package]] 533 | name = "wasm-bindgen-backend" 534 | version = "0.2.95" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 537 | dependencies = [ 538 | "bumpalo", 539 | "log", 540 | "once_cell", 541 | "proc-macro2", 542 | "quote", 543 | "syn 2.0.85", 544 | "wasm-bindgen-shared", 545 | ] 546 | 547 | [[package]] 548 | name = "wasm-bindgen-macro" 549 | version = "0.2.95" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 552 | dependencies = [ 553 | "quote", 554 | "wasm-bindgen-macro-support", 555 | ] 556 | 557 | [[package]] 558 | name = "wasm-bindgen-macro-support" 559 | version = "0.2.95" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 562 | dependencies = [ 563 | "proc-macro2", 564 | "quote", 565 | "syn 2.0.85", 566 | "wasm-bindgen-backend", 567 | "wasm-bindgen-shared", 568 | ] 569 | 570 | [[package]] 571 | name = "wasm-bindgen-shared" 572 | version = "0.2.95" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 575 | 576 | [[package]] 577 | name = "windows-core" 578 | version = "0.52.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 581 | dependencies = [ 582 | "windows-targets", 583 | ] 584 | 585 | [[package]] 586 | name = "windows-targets" 587 | version = "0.52.6" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 590 | dependencies = [ 591 | "windows_aarch64_gnullvm", 592 | "windows_aarch64_msvc", 593 | "windows_i686_gnu", 594 | "windows_i686_gnullvm", 595 | "windows_i686_msvc", 596 | "windows_x86_64_gnu", 597 | "windows_x86_64_gnullvm", 598 | "windows_x86_64_msvc", 599 | ] 600 | 601 | [[package]] 602 | name = "windows_aarch64_gnullvm" 603 | version = "0.52.6" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 606 | 607 | [[package]] 608 | name = "windows_aarch64_msvc" 609 | version = "0.52.6" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 612 | 613 | [[package]] 614 | name = "windows_i686_gnu" 615 | version = "0.52.6" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 618 | 619 | [[package]] 620 | name = "windows_i686_gnullvm" 621 | version = "0.52.6" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 624 | 625 | [[package]] 626 | name = "windows_i686_msvc" 627 | version = "0.52.6" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 630 | 631 | [[package]] 632 | name = "windows_x86_64_gnu" 633 | version = "0.52.6" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 636 | 637 | [[package]] 638 | name = "windows_x86_64_gnullvm" 639 | version = "0.52.6" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 642 | 643 | [[package]] 644 | name = "windows_x86_64_msvc" 645 | version = "0.52.6" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 648 | 649 | [[package]] 650 | name = "winnow" 651 | version = "0.5.40" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 654 | dependencies = [ 655 | "memchr", 656 | ] 657 | --------------------------------------------------------------------------------