Skip to content

Commit 0b93f96

Browse files
committed
add everything
1 parent 14a174c commit 0b93f96

31 files changed

+1583
-5
lines changed

Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
[workspace]
22
members = [
3+
"examples",
34
"scylla-bindgen",
5+
"scylla-bindgen-macros",
46
"scylla-cql",
7+
"tests",
58
]
9+
10+
[workspace.package]
11+
edition = "2021"
12+
version = "0.0.1"
13+
repository = "https://github.com/wmitros/scylla-rust-udf"
14+
license = "MIT OR Apache-2.0"
15+
rust-version = "1.66.1"
16+
17+
[workspace.dependencies]
18+
scylla-bindgen = { path = "scylla-bindgen" }
19+
scylla-bindgen-macros = { path = "scylla-bindgen-macros" }

README.md

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,100 @@
1-
# Rust-utils-for-Scylla-UDFs
1+
# Rust helper library for Scylla UDFs
2+
3+
## Usage
4+
5+
### Prerequisites
6+
7+
To use this helper library in Scylla you'll need:
8+
* Standard library for Rust `wasm32-wasi`
9+
Can be added in rustup installations using `rustup target add wasm32-wasi`
10+
For non rustup setups, you can try following the steps at https://rustwasm.github.io/docs/wasm-pack/prerequisites/non-rustup-setups.html
11+
Also available as an rpm: `rust-std-static-wasm32-wasi`
12+
* `wasm2wat` parser
13+
Available in many distributions in the `wabt` package
14+
15+
### Compilation
16+
17+
We recommend a setup with cargo.
18+
19+
1. Start with a library package
20+
```
21+
cargo new --lib
22+
```
23+
2. Edit the Cargo.toml to set the crate-type to cdylib
24+
```
25+
+ [lib]
26+
+ crate-type = ["cdylib"]
27+
```
28+
3. Implement your package, exporting Scylla UDFs using the `scylla_bindgen::export_udf` macro.
29+
4. Build the package using the wasm32-wasi target:
30+
```
31+
cargo build --target=wasm32-wasi
32+
```
33+
5. Find the compiled `.wasm` binary. Let's assume it's `target/wasm32-wasi/debug/abc.wasm`.
34+
6. (optional) Optimize the binary using `wasm-opt -O3 target/wasm32-wasi/debug/abc.wasm` (can be combined with using `cargo build --release` profile)
35+
7. Translate the binary into `wat`:
36+
```
37+
wasm2wat target/wasm32-wasi/debug/abc.wasm > target/wasm32/wasi/debug/abc.wat
38+
```
39+
40+
### CQL Statement
41+
42+
The resulting `target/wasm32/wasi/debug/abc.wat` code can now be used directly in a `CREATE FUNCTION` statement. The resulting code will most likely
43+
contain `'` characters, so it's recommended to use a `LANGUAGE xwasm AS $$ (module ...) $$` clause instead of `LANGUAGE xwasm AS ' (module ...) '`.
44+
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:
45+
```
46+
CREATE FUNCTION commas(string list<text>) CALLED ON NULL INPUT RETURNS text AS $$ (module ...) $$
47+
```
48+
49+
50+
## CQL Type Mapping
51+
52+
### Native types
53+
54+
| CQL Type | Rust type |
55+
| --------- | ----------------------------- |
56+
| ASCII | String |
57+
| BIGINT | i64 |
58+
| BLOB | Vec\<u8\> |
59+
| BOOLEAN | bool |
60+
| COUNTER | scylla_bindgen::Counter |
61+
| DATE | chrono::NaiveDate |
62+
| DECIMAL | bigdecimal::Decimal |
63+
| DOUBLE | f64 |
64+
| DURATION | scylla_bindgen::CqlDuration |
65+
| FLOAT | f32 |
66+
| INET | std::net::IpAddr |
67+
| INT | i32 |
68+
| SMALLINT | i16 |
69+
| TEXT | String |
70+
| TIME | scylla_bindgen::Time |
71+
| TIMESTAMP | scylla_bindgen::Timestamp |
72+
| TIMEUUID | uuid::Uuid |
73+
| TINYINT | i8 |
74+
| UUID | uuid::Uuid |
75+
| VARCHAR | String |
76+
| VARINT | num_bigint::BigInt |
77+
78+
### Collections
79+
80+
If a CQL type `T` maps to Rust type `RustT`, you can use it as a collection parameter:
81+
82+
| CQL Type | Rust type |
83+
| ---------- | ----------------------------------------------------------------------- |
84+
| LIST\<T\> | Vec\<RustT\> |
85+
| MAP\<T\> | std::collections::BTreeMap\<RustT\>, std::collections::HashMap\<RustT\> |
86+
| SET\<T\> | std::collections::BTreeSet\<RustT\>, std::collections::HashSet\<RustT\> |
87+
88+
89+
### Tuples
90+
91+
If CQL types `T1`, `T2`, ... map to Rust types `RustT1`, `RustT2`, ..., you can use them in tuples:
92+
93+
| CQL Type | Rust type |
94+
| -------- | ---------------------------------- |
95+
| TUPLE\<T1, T2, ...\> | (RustT1, RustT2, ...) |
96+
97+
### Nulls
98+
99+
If a CQL Value of type T, that's mapped to type RustT, may be a null (possible in non-`RETURNS NULL ON NULL INPUT` UDFs),
100+
the type used in the Rust function should be Option\<RustT\>.

examples/Cargo.toml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
[package]
2+
name = "examples"
3+
edition.workspace = true
4+
version.workspace = true
5+
repository.workspace = true
6+
license.workspace = true
7+
rust-version.workspace = true
8+
publish = false
9+
10+
[dependencies]
11+
chrono = "0.4"
12+
bigdecimal = "0.2.0"
13+
num-bigint = "0.3"
14+
scylla-bindgen = { workspace = true }
15+
uuid = "1.0"
16+
17+
[[example]]
18+
name = "add"
19+
path = "add.rs"
20+
crate-type = ["cdylib"]
21+
22+
[[example]]
23+
name = "combine"
24+
path = "combine.rs"
25+
crate-type = ["cdylib"]
26+
27+
[[example]]
28+
name = "commas"
29+
path = "commas.rs"
30+
crate-type = ["cdylib"]
31+
32+
[[example]]
33+
name = "dbl"
34+
path = "dbl.rs"
35+
crate-type = ["cdylib"]
36+
37+
[[example]]
38+
name = "fib"
39+
path = "fib.rs"
40+
crate-type = ["cdylib"]
41+
42+
[[example]]
43+
name = "keys"
44+
path = "keys.rs"
45+
crate-type = ["cdylib"]
46+
47+
[[example]]
48+
name = "len"
49+
path = "len.rs"
50+
crate-type = ["cdylib"]
51+
52+
[[example]]
53+
name = "topn"
54+
path = "topn.rs"
55+
crate-type = ["cdylib"]
56+
57+
[[example]]
58+
name = "udt"
59+
path = "udt.rs"
60+
crate-type = ["cdylib"]
61+
62+
[[example]]
63+
name = "wordcount"
64+
path = "wordcount.rs"
65+
crate-type = ["cdylib"]

examples/add.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use scylla_bindgen::export_udf;
2+
3+
type SmallInt = i16;
4+
5+
#[export_udf]
6+
fn add(i1: SmallInt, i2: SmallInt) -> SmallInt {
7+
i1 + i2
8+
}

examples/combine.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use scylla_bindgen::{export_udf, Counter, 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<u8>,
8+
cnt: Counter,
9+
date: chrono::NaiveDate,
10+
bd: bigdecimal::BigDecimal,
11+
dbl: f64,
12+
cqldur: CqlDuration,
13+
flt: f32,
14+
int32: i32,
15+
int64: i64,
16+
s: String,
17+
tstamp: Timestamp,
18+
ip: std::net::IpAddr,
19+
int16: i16,
20+
int8: i8,
21+
tim: Time,
22+
uid: uuid::Uuid,
23+
bi: num_bigint::BigInt,
24+
) -> (
25+
(
26+
bool,
27+
Vec<u8>,
28+
Counter,
29+
chrono::NaiveDate,
30+
bigdecimal::BigDecimal,
31+
f64,
32+
CqlDuration,
33+
f32,
34+
i32,
35+
i64,
36+
),
37+
(
38+
String,
39+
Timestamp,
40+
std::net::IpAddr,
41+
i16,
42+
i8,
43+
Time,
44+
uuid::Uuid,
45+
num_bigint::BigInt,
46+
),
47+
) {
48+
(
49+
(b, blob, cnt, date, bd, dbl, cqldur, flt, int32, int64),
50+
(s, tstamp, ip, int16, int8, tim, uid, bi),
51+
)
52+
}

examples/commas.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use scylla_bindgen::export_udf;
2+
3+
#[export_udf]
4+
fn commas(strings: Option<Vec<String>>) -> Option<String> {
5+
strings.map(|strings| strings.join(", "))
6+
}

examples/dbl.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use scylla_bindgen::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+
}

examples/fib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use scylla_bindgen::*;
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+
}

examples/keys.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use scylla_bindgen::export_udf;
2+
3+
#[export_udf]
4+
fn keys(map: std::collections::BTreeMap<String, String>) -> Vec<String> {
5+
map.into_keys().collect()
6+
}

examples/len.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use scylla_bindgen::export_udf;
2+
3+
#[export_udf]
4+
fn len(strings: std::collections::BTreeSet<String>) -> i16 {
5+
strings.len() as i16
6+
}

examples/topn.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use scylla_bindgen::*;
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.len() == other.0.len()
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<std::cmp::Ordering> {
17+
Some(self.0.len().cmp(&other.0.len()))
18+
}
19+
}
20+
21+
impl std::cmp::Ord for StringLen {
22+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
23+
self.0.len().cmp(&other.0.len())
24+
}
25+
}
26+
27+
// Store the top N strings by length, without repetitions.
28+
#[export_udf]
29+
fn topn_row((n, mut acc): (i32, BTreeSet<StringLen>), v: StringLen) -> (i32, BTreeSet<StringLen>) {
30+
acc.insert(v);
31+
while acc.len() > n as usize {
32+
acc.pop_first();
33+
}
34+
35+
(n, acc)
36+
}
37+
38+
#[export_udf]
39+
fn topn_reduce(
40+
(n1, mut acc1): (i32, BTreeSet<StringLen>),
41+
(n2, mut acc2): (i32, BTreeSet<StringLen>),
42+
) -> (i32, BTreeSet<StringLen>) {
43+
assert!(n1 == n2);
44+
acc1.append(&mut acc2);
45+
while acc1.len() > n1 as usize {
46+
acc1.pop_first();
47+
}
48+
(n1, acc1)
49+
}
50+
51+
#[export_udf]
52+
fn topn_final((_, acc): (i32, BTreeSet<StringLen>)) -> BTreeSet<StringLen> {
53+
acc
54+
}

examples/udt.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use scylla_bindgen::*;
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+
}

examples/wordcount.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use scylla_bindgen::export_udf;
2+
3+
#[export_udf]
4+
fn wordcount(text: String) -> i32 {
5+
text.split(' ').count() as i32
6+
}

0 commit comments

Comments
 (0)