Overview
This tutorial will teach you how to write WebAssembly (Wasm) UDFs in in C/C++ and Rust, load them into SingleStoreDB, and evaluate them in queries.
Let's get started by learning how to setup our environment!
Setup
For each example in this tutorial, it will be useful to create yourself a separate work directory in which to put your code and compiled artifacts. This will help keep your files for each example organized.
In addition to a SQL client and SingleStore database, you’ll need a few other things to get your environment set up properly. For this, you have some options:
- Use a development container (recommended)
- Install development dependencies locally
Setting Up a Development Container
If you have a container runtime (e.g. Docker) available to you, the easiest way to get started is to utilize one of our prebuilt Wasm development containers. To do this, first clone or download the SingleStore WASM Toolkit repository:
git clone git@github.com:singlestore-labs/singlestore-wasm-toolkit.git
The development containers provided in this repo contain all of the tools you'll need to write, build, and deploy Wasm UDFs. Additionally, the repo also includes many examples, including fully-written versions of the examples referenced in this tutorial. You may choose from the following containers, depending on your preferred workflow:
The exact setup for each of these is described in their respective sections.
VS Code Development Container Setup
This container is designed to integrate natively into VS Code using the Remote Containers extension. VS Code an IDE framework that is usable run on Mac, Windows, or Linux, and can be downloaded here.
Note MacOS Users: VS Code is usable on both x64 and M1 Macs. One way to install it for MacOS is to use the package manager brew. To get brew, please follow the instuctions here. When you have brew, run
brew install --cask visual-studio-code
to obtain VS Code.
-
Ensure that VS Code is installed, and the wasm-toolkit repository has been cloned.
-
In VS Code, install the Remote - Containers VS Code Extension if you do not already have it.
-
In VS Code, type F1 and search for “Open Folder in Container”.
-
Navigate to the directory where you cloned the wasm-toolkit repo, and click Open.
-
VS Code will now build the container, which may take quite a while.
When the container build has completed, you should see wasm-toolkit repository's file tree appear in the EXPLORER panel on the left side of the VS Code window. For the purposes of this tutorial, it will be most convenient if you store your code in a directory relative to the repository. Here's one way to do this:
-
Right-click on the empty space in the EXPLORER panel, and select
New Folder
. -
Type
wasm-tutorial
and press Enter. There should now be a sub-folder by this name in the tree. Put any files you create in here.
Note As you work through this tutorial, please be sure to execute your commands inside the VS Code terminal window so that you have access to the necessary tools.
Next, let's pick an example to work through.
Standalone Container Setup
This container provides a standalone shell environment configured with a variety of Wasm development tools. It will mount the directory of your choosing.
-
Ensure that the wasm-toolkit repository has been cloned.
-
Create a subdirectory for the code in this tutorial. This can be anywhere. For the purposes of this tutorial, we'll assume it is in
/home/$USER/wasm-tutorial
. -
At your command prompt, change to the root directory of the wasm-toolkit repository and type
scripts/dev-shell /home/$USER/wasm-tutorial
. Ensure that the argument reflects the actual path of the directory you created in the above step. -
You should now see the following prompt:
[dev-shell]:~/src %
The src
directory has been mounted from /home/$USER/wasm-tutorial
(or whatever alternative directory you specified in step 3). It is not necessary to write the code for this tutorial inside the container's shell; you may use your preferred editing workflow for this. However, please do be sure to run all suggested build and deployment commands inside the container's shell so that you have access to the necessary tools.
Next, let's pick an example to work through.
Local Setup
If you do not wish to use a development container and/or you’d prefer to set up your development environment on your local system, you’ll need to do the following:
-
Download the WASI SDK and decompress it somewhere. Ensure that your
$PATH
variable is prefixed with this location when you are running the build commands suggested in this tutorial. For example, assuming you installed the WASI SDK in/opt/wasi-sdk
, at the command prompt, you can type (assuming your are usingbash
/zsh
):export PATH=/opt/wasi-sdk/bin:$PATH
-
Download and install the Rust toolchain:
-
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-
source $HOME/.cargo/env
-
-
Download and install the wit-bindgen program:
cargo install --git https://github.com/bytecodealliance/wit-bindgen wit-bindgen-cli
-
Download and install the cargo-wasi plugin:
cargo install cargo-wasi
-
(Optional) If you would like to use the testing tool
writ
and the deployment toolpushwasm
, please follow the installation instructions in their respective repositories and ensure they are in your$PATH
.
For a more detailed walkthrough on setting up for local development, also consider checking out David Lee's blog entry here.
Next, let's pick an example to work through.
Examples
In this tutorial, we'll walk you through two different examples.
power-of
This is a very small example that will show you the basics of creating a Wasm UDF. In it, we will develop a program that computes x^y (that is, "x to the power of y").
You can get started on this tutorial here.
split-str
This is a more advanced example in which we will write a Wasm TVF that splits a string into substring given a delimiting character. It will show you how to develop a Wasm function that accepts and returns complex structures such as strings and records.
You can get started on this tutorial here.
Creating a WIT Specification
- To see how to create a WIT specification for the
power-of
example, please look here. - To see how to create a WIT specification for the
split-str
example, please look here.
Creating a WIT Specification for the power-of
Example
Before we do any coding, let’s first define our interface. WIT is an Interface Definition Language (IDL) used to describe Wasm modules. It is provided in files with the .wit
extension.
This example will compute x^y (that is, "x to the power of y").
In a new work directory, create a new file called power.wit
in your text editor, and add this content:
power-of: func(b: s32, exp: s32) -> s32
This function will take two signed 32-bit integers as arguments (the base b
and the exponent exp
) and return a single, signed 32-bit integer.
Go ahead and save the file.
Now we’re ready to write some code.
- If you'd like to learn about implementing this example in C, please look here.
- If you'd like to learn about implementing this example in Rust, please look here.
Creating a WIT Specification for the split-str
Example
Before we do any coding, let’s first define our interface. WIT is an Interface Definition Language (IDL) used to describe Wasm modules. It is provided in files with the .wit
extension.
In this example, we’ll work with strings and nested types. Let’s create a function that takes a string, splits it at every occurrence of a delimiting string, and then returns each sub-string along with its starting indices. The output will be sent back as a list of records (aka structures).
To start, let’s create a new work directory, and inside of it we’ll make a new file called split.wit
. The WIT IDL we need is below, so we can go ahead and paste that in and save it.
record subphrase {
str: string,
idx: s32
}
split-str: func(phrase: string, delim: string) -> list<subphrase>
Now we’re ready to write some code.
- If you'd like to learn about implementing this example in C++, please look here.
- If you'd like to learn about implementing this example in Rust, please look here.
Implementation and Compilation
- To see how to implement and compile a Wasm function in C/C++, please look here.
- To see how to implement and compile a Wasm function in Rust, please look here.
Implementing and Compiling Examples in C/C++
- To see how to implement and compile the
power-of
example in C, please look here. - To see how to implement and compile the
split-str
example in C++, please look here.
Implementing the power-of
Example in C
Generating Bindings
For the C language, we’ll need to explicitly generate language bindings for our functions. We can do this using the wit-bindgen
program. In your work directory, run the following command:
wit-bindgen c --export power.wit
This will generate two files in your work directory: power.c
and power.h
.
If you look at the contents of the power.h
file, you’ll see a single prototype:
int32_t power_power_of(int32_t base, int32_t exp);
The name looks odd because wit-bindgen concatenates the name of the WIT file with the name of the function. That’s ok; as we’ll see in a moment, the name that is actually exported will make more sense.
Aside from the function name, the signature is as expected: take two 32-bit integers, and return one.
Next, open the power.c
file in your editor. We’ll ignore the canonical_abi_realloc
and canonical_abi_free
functions for now and skip to the bottom where we will find a function called __wasm_export_power_power_of
. This is wrapper code that handles passing values through the Wasm Canonical ABI (a trivial operation in this case). Looking at the body of this function, we can see that it calls the power_power
function that was declared in the header file. We’ll need to provide the implementation for this.
Before we continue, though, notice the following line just above the this function’s definition:
__attribute__((export_name("power-of")))
This line forces the name of this wrapper function be exported from the compiled module as power-of (the hyphen is the preferred word separator for function names in Wasm). Fortunately, consumers only need to invoke it by this name.
Implementing and Compiling
Now, let’s implement the logic we need. To the bottom of the power.c
file, add the following code (we can start by copying in the prototype from the power.h
file):
int32_t power_power_of(int32_t base, int32_t exp)
{
int32_t res = 1;
for (int32_t i = 0; i < exp; ++i)
{
res *= base;
}
return res;
}
Now let’s save the file and get back to our command line.
We can compile this program into a Wasm module by using the following command.
clang \
--target=wasm32-unknown-wasi \
-mexec-model=reactor \
-I. \
-o power.wasm \
power.c
You should now see a power.wasm
file in your directory.
Next, we'll do some testing.
Implementing the split-str
example in C++
Generating Bindings
In this example, we’ll use C++ so that we can leverage the STL’s higher-level data structures and keep our implementation focused on the big picture as much as possible.
As before, we’ll start by generating the C language bindings. Run the wit-bindgen command:
wit-bindgen c --export split.wit
There should now be a split.c
and split.h
file in your work directory. Since we’ll be using C++, rename split.c
to split.cpp
:
Implementing and Compiling
Since we’ll be using C++, rename split.c
to split.cpp
:
mv split.c split.cpp
Now let’s take a look at split.h
. As we might expect, there are a few more definitions in here than in the simpler power-of
example. The wit-bindgen program has generated a struct definition for us to use when passing our strings, as well as ones for the subphrase record and its enclosing list. At the bottom is prototype for the function we are going to implement:
void split_split_str(split_string_t *phrase, split_string_t *delim, split_list_subphrase_t *ret0);
This, too, looks a bit different than in the power-of
example. For one thing, the function doesn’t return a value. Since this function will be returning a list, for which we’ll need to dynamically allocate memory, the result is passed as an argument pointer instead.
Now let’s open up the split.cpp
file. Once again, we are going to add our implementation here. At the bottom of the file, we can find the generated wrapper function, called __wasm_export_split_split_str
. This wrapper delegates to the function we will be implementing, and we also can see that it is doing work required for lifting and lowering the data types on either side of the function call.
We’ll now add our code. Let’s first update the top of split.cpp
as follows:
#include <memory>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <vector>
#include <split.h>
Since C++ has a more strict compiler than C, we’ll also need to make a small change to the generated code in this file at line 38. Go ahead and change the following line:
ret->ptr = canonical_abi_realloc(NULL, 0, 1, ret->len);
... to this:
ret->ptr = reinterpret_cast<char *>(canonical_abi_realloc(NULL, 0, 1, ret->len));
And finally, to the bottom of the file, we’ll add this chunk of code:
void split_split_str(split_string_t *phrase, split_string_t *delim, split_list_subphrase_t *ret0)
{
// Clear the result.
memset(ret0, 0, sizeof(split_list_subphrase_t));
// Parse the tokens.
std::string phr(phrase->ptr, phrase->len);
std::string dlm(delim->ptr, delim->len);
std::string tok;
std::vector<std::pair<std::string, size_t>> subs;
size_t start = 0, end = 0;
if (delim->len)
{
while ((end = phr.find(dlm, start)) != std::string::npos)
{
tok = phr.substr(start, end - start);
subs.push_back(std::pair<std::string, size_t>(tok, start));
start = end + dlm.length();
}
}
subs.push_back(std::pair<std::string, size_t>(phr.substr(start), start));
// Populate the result.
bool err = false;
auto res = (split_subphrase_t *) malloc(phr.size() * sizeof(split_subphrase_t));
for (int i = 0; !err && i < subs.size(); ++i)
{
auto& sub = subs[i].first;
res[i].idx = static_cast<int32_t>(subs[i].second);
res[i].str.len = sub.length();
res[i].str.ptr = strdup(sub.c_str());
if (!res[i].str.ptr)
err = true;
}
// If success, assign the result. Else, clean up and return an empty list.
if (!err)
{
// Success; assign the result.
ret0->ptr = res;
ret0->len = subs.size();
}
else
{
if (res)
{
for (int i = 0; i < subs.size(); ++i)
if (res[i].str.ptr)
free(res[i].str.ptr);
free(res);
}
}
// Per the Canonical ABI contract, free the input pointers.
free(phrase->ptr);
free(delim->ptr);
}
There is much more work going on here than in the power-of
example; a fair amount of it deals with memory management.
The Wasm Canonical ABI requires that any dynamic memory passed from the host to the guest or vice-versa transfers the ownership to the receiver. This explains the last two lines of our split_split_str
function, where we free the pointers to the strings we have been passed as arguments. This memory will have been allocated by the host using our guest module’s exported canonical_abi_realloc
function, which takes it from the Wasm instance’s linear memory at runtime. Conversely, you may also notice that we pass dynamically allocated memory out of the function; the host is expected to free it by calling our module's canonical_abi_free
routine, which will return it to the linear memory.
Now let’s save the file and build the module. We will use a similar approach as we did in the simple example above, but with a couple of tweaks. First, we’ll use clang++, since this is C++ code. And second, we’ll need to include the option -fno-exceptions
because Wasm doesn’t yet support exception handling (there is a proposal, however).
clang++ \
-fno-exceptions \
--target=wasm32-unknown-wasi \
-mexec-model=reactor \
-s \
-I. \
-o split.wasm \
split.cpp
As expected, there should now be a split.wasm
file in your work directory.
Next, we'll do some testing.
Implementing and Compiling Examples in Rust
- To see how to implement and compile the
power-of
example in Rust, please look here. - To see how to implement and compile the
split-str
example in Rust, please look here.
Developing the power-of
example in Rust
Initialize Your Source Tree
To setup our project, we'll need to do a few things:
-
Ensure that you are in a new directory. It can be called anything you want.
-
In the work directory, run the following to set up a skeltal Rust source tree:
cargo init --vcs none --lib
-
Now, create a special configuration file to tell utilities like the rust-analyzer that the default build target of this project is Wasm. Run this command:
mkdir .cargo && echo -e "[build]\ntarget = \"wasm32-wasi\"\n" > .cargo/config.toml
Copy the WIT File
We’ll need the power.wit
file we created earlier. Copy it into this directory if it is not already there.
Implementing and Compiling
Now, edit the file called Cargo.toml
so that it looks like the following:
[package]
name = "power"
version = "0.1.0"
edition = "2018"
[dependencies]
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen.git", rev = "60e3c5b41e616fee239304d92128e117dd9be0a7" }
[lib]
crate-type = ["cdylib"]
Now we’re almost ready to roll. Edit the file src/lib.rs
and replace its content with this:
wit_bindgen_rust::export!("power.wit");
struct Power;
impl power::Power for Power {
fn power_of(base: i32, exp: i32) -> i32 {
let mut res = 1;
for _i in 0..exp {
res *= base;
}
res
}
}
The syntax at the top of the code is boiler-plate. The export macro generates code that declares a trait named after our WIT file (and some other things). So, our main job is here is to implement this trait. If you are curious what the macro actually generates, you can run cargo expand and it will show you the fully expanded source code.
The WIT IDL is heavily inspired by the Rust language syntax, so it was pretty easy to derive the Rust function signature we needed from the IDL:
-
Replaced hyphens with underscores
-
Changed s32 types to i32
Now we can compile the program into a wasm module using this command:
cargo wasi build --lib
The new Wasm file should be written to target/wasm32-wasi/debug/power.wasm
.
Next, we'll do some testing.
Developing the split-str
example in Rust
Initialize Your Source Tree
To setup our project, we'll need to do a few things:
-
Ensure that you are in a new directory. It can be called anything you want.
-
In the work directory, run the following to set up a skeltal Rust source tree:
cargo init --vcs none --lib
-
Now, create a special configuration file to tell utilities like the rust-analyzer that the default build target of this project is Wasm. Run this command:
mkdir .cargo && echo -e "[build]\ntarget = \"wasm32-wasi\"\n" > .cargo/config.toml
Copy the WIT File
We'll need the split.wit
file we created earlier. Copy it into this directory if it is not already there.
Implementing and Compiling
Much of this will be similar to the techniques we used in the power-of
example.
Edit the Cargo.toml
file so it looks like this:
[package]
name = "split"
version = "0.1.0"
edition = "2018"
[dependencies]
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen.git", rev = "60e3c5b41e616fee239304d92128e117dd9be0a7" }
[lib]
crate-type = ["cdylib"]
And, for the implementation, edit the src/lib.rs
file and replace its contents with this:
wit_bindgen_rust::export!("split.wit");
struct Split;
use crate::split::Subphrase;
impl split::Split for Split {
fn split_str(phrase: String, delim: String) -> Vec<Subphrase> {
phrase
.split(&delim)
.scan(0, |idx, s| {
let current = Subphrase {
str: s.to_string(),
idx: *idx as i32
};
*idx += (s.0 + delim.0) as i32;
Some(current)
})
.collect()
}
}
Fortunately, with Rust, we have some nice language features that help make our code concise. Unlike in C/C++
, we don’t need to explicitly free the parameters. Due to Rust’s move semantics, they will be freed implicitly when the function ends. Allocation for the output vector is also managed “under the hood” by Rust’s robust data structures and runtime. Finally, the declaration of the Subphrase struct happens via the wit_bindgen_rust::export macro
, so we don’t need to do it.
Let’s compile the Wasm module now:
cargo wasi build --lib
The new Wasm file should be written to target/wasm32-wasi/debug/split.wasm
.
Next, we'll do some testing.
Testing
- To see how to test the
power-of
example, please look here. - To see how to test the
split-str
example, please look here.
Testing the power-of
Example
The writ
utility provides a convenient way for us to test our Wasm function in isolation before we load it into the database. It accepts JSON-formatted arguments on the command-line, casts them to the actual types defined in the Wasm function, and then passes them in.
Let's make sure our power-of
program is working correctly by doing a few spot-tests. The examples below assume that the power.wasm
file exists in the current directory. If you are using a Rust build, your Wasm file will be located at target/wasm32-wasi/debug/power.wasm
.
$ writ --wit power.wit ./power.wasm power-of 2 3
8
$ writ --wit power.wit ./power.wasm power-of 2 0
1
$ writ --wit power.wit ./power.wasm power-of 0 0
1
$ writ --wit power.wit ./power.wasm power-of 0 2
0
$ writ --wit power.wit ./power.wasm power-of 2 -3
1
Except for the last attempt, the function seems to work correctly. To keep this example simple, we'll just assume that negative exponents won't be provided.
Now, we're ready to deploy.
Testing the split-str
Example
The writ
utility provides a convenient way for us to test our Wasm function in isolation before we load it into the database. It accepts JSON-formatted arguments on the command-line, casts them to the actual types defined in the Wasm function, and then passes them in.
Let's make sure our split-str
program is working correctly by doing a few spot-tests. The examples below assume that the split.wasm
file exists in the current directory. If you are using a Rust build, your Wasm file will be located at target/wasm32-wasi/debug/split.wasm
.
$ writ --wit split.wit ./split.wasm split-str 'hello_you_fine_folks' '_'
[
{
"str": "hello",
"idx": 0
},
{
"str": "you",
"idx": 6
},
{
"str": "fine",
"idx": 10
},
{
"str": "folks",
"idx": 15
}
]
Let's try a couple of edge cases as well.
$ writ --wit split.wit ./split.wasm split-str 'hello' '_'
[
{
"str": "hello",
"idx": 0
}
]
$ writ --wit split.wit split.wasm split-str 'hello--there-' '-'
[
{
"str": "hello",
"idx": 0
},
{
"str": "",
"idx": 6
},
{
"str": "there",
"idx": 7
},
{
"str": "",
"idx": 13
}
]
Looks good!
Now, we're ready to deploy.
Deploying Examples into SingleStoreDB
- For information on deploying the
power-of
example, please look here. - For information on deploying the
split-str
example, please look here.
Deploying the power-of
Example
Now that you've compiled and tested your Wasm function, it is ready for deployment into the database. This can be done in multiple ways. The easiest is probably to use the pushwasm
tool, included in the wasm-toolkit
development containers. Alternatively, you can "pull" the Wasm module into the database by first uploading it to cloud storage (SingleStoreDB supports pulling Wasm modules from multiple cloud providers -- GCS, Azure, and S3). We'll discuss both techniques.
Before we start, ensure that a destination database is available. To do this, using your favorite SQL client, create a new database called wasm_tutorial
. For example, you might use the following statements:
CREATE DATABASE wasm_tutorial;
USE wasm_tutorial;
- To deploy the
power-of
example using thepushwasm
tool, please see here. - To deploy the
power-of
example from cloud storage, please see here.
Deploying power-of
Using pushwasm
The pushwasm
tool will upload our WIT file and compiled Wasm module into the database. To use it, we'll need the following information. Since this depends highly on your specific environment, we'll just make some generic assumptions about their values.
- The hostname of the SingleStoreDB server (we'll call this
myserver
) - The destination database name (we'll call this
wasm_tutorial
) - The user ID and password of the database user (we'll call this user
admin
) - The path to the compiled Wasm module (we'll use
./power.wasm
below, but for the Rust example, you should use located intarget/wasm32-wasi/debug/power.wasm
instead) - The path to the WIT file
Now, run the following command from within your work directory:
pushwasm --prompt --wit ./power.wit mysql://admin@myserver/wasm_tutorial ./power.wasm power_of
The --prompt
option will cause a prompt to appear, where you can enter your database user's password.
When the deployment has completed, you should see the following:
Wasm UDF 'power_of' was created successfully.
Finally, we're ready to run the UDF!
Deploying power-of
From Cloud Storage
You'll need the following information to do this:
- The path to the compiled Wasm module (we'll use
./power.wasm
below, but for the Rust example, you should use located intarget/wasm32-wasi/debug/power.wasm
instead) - The path to the WIT file
- Your cloud storage credentials and path (we'll assume an S3 bucket called
wasm-modules
).
From your work directory, you'll need to upload your WIT (./power.wit
) and compiled Wasm (.wasm
) files to a bucket to the wasm-modules
S3 bucket.
Now, connect to the database from your SQL client and run the following statement to pull the module from S3 into the wasm_tutorial
database:
CREATE FUNCTION power_of AS WASM
FROM S3 'wasm-modules/power.wasm'
CREDENTIALS '{
"aws_access_key_id": "ASIAZPIKLSJ3HM7FKAUB",
"aws_secret_access_key": FwoGZXIvYXdzEL3fv [...]"
}'
CONFIG '{"region": "us-east-1"}'
WITH WIT FROM S3 'wasm-modules/power.wit'
CREDENTIALS '{
"aws_access_key_id": "ASIAZPIKLSJ3HM7FKAUB",
"aws_secret_access_key": FwoGZXIvYXdzEL3fv [...]"
}'
CONFIG '{"region": "us-east-1"}';
If the UDF has been created successfully, you will see something like:
Query OK, 1 row affected (0.029 sec)
Finally, we're ready to run the UDF!
Deploying the split-str
Example
Now that you've compiled and tested your Wasm function, it is ready for deployment into the database. This can be done in multiple ways. The easiest is probably to use the pushwasm
tool, included in the wasm-toolkit
development containers. Alternatively, you can "pull" the Wasm module into the database by first uploading it to cloud storage (SingleStoreDB supports pulling Wasm modules from multiple cloud providers -- GCS, Azure, and S3). We'll discuss both techniques.
Before we start, ensure that a destination database is available. To do this, using your favorite SQL client, create a new database called wasm_tutorial
. For example, you might use the following statements:
CREATE DATABASE wasm_tutorial;
USE wasm_tutorial;
- To deploy the
split-str
example using thepushwasm
tool, please see here. - To deploy the
split-str
example from cloud storage, please see here.
Deploying split-str
Using pushwasm
The pushwasm
tool will upload our WIT file and compiled Wasm module into the database. To use it, we'll need the following information. Since this depends highly on your specific environment, we'll just make some generic assumptions about their values.
- The hostname of the SingleStoreDB server (we'll call this
myserver
) - The destination database name (we'll call this
wasm_tutorial
) - The user ID and password of the database user (we'll call this user
admin
) - The path to the compiled Wasm module (we'll use
./power.wasm
below, but for the Rust example, you should use located intarget/wasm32-wasi/debug/power.wasm
instead) - The path to the WIT file
Now, run the following command from within your work directory. Unlike the power-of
example, we'll deploy the split-str
function as a Table-Valued Function (TVF). This will require us to pass the --tvf
flag.
pushwasm --tvf --prompt --wit ./split.wit mysql://admin@myserver/wasm_tutorial ./split.wasm split_str
The --prompt
option will cause a prompt to appear, where you can enter your database user's password.
When the deployment has completed, you should see the following:
Wasm TVF 'split_str' was created successfully.
Finally, we're ready to run the TVF!
Deploying split-str
From Cloud Storage
You'll need the following information to do this:
- The path to the compiled Wasm module (we'll use
./split.wasm
below, but for the Rust example, you should use located intarget/wasm32-wasi/debug/split.wasm
instead) - The path to the WIT file
- Your cloud storage credentials and path (we'll assume an S3 bucket called
wasm-modules
).
From your work directory, you'll need to upload your WIT (./split.wit
) and compiled Wasm (./split.wasm
) files to a bucket to the wasm-modules
S3 bucket.
Now, connect to the database from your SQL client and run the following statement to pull the module from S3 into the wasm_tutorial
database. Note that unlike the power-of
example, we'll be deploying this split-str
function as a Table-Valued Function (TVF). This will require us to include the RETURNS TABLE
clause.
CREATE FUNCTION split_str RETURNS TABLE AS WASM
FROM S3 'wasm-modules/split.wasm'
CREDENTIALS '{
"aws_access_key_id": "ASIAZPIKLSJ3HM7FKAUB",
"aws_secret_access_key": FwoGZXIvYXdzEL3fv [...]"
}'
CONFIG '{"region": "us-east-1"}'
WITH WIT FROM S3 'wasm-modules/split.wit'
CREDENTIALS '{
"aws_access_key_id": "ASIAZPIKLSJ3HM7FKAUB",
"aws_secret_access_key": FwoGZXIvYXdzEL3fv [...]"
}'
CONFIG '{"region": "us-east-1"}';
If the TVF has been created successfully, you will see something like:
Query OK, 1 row affected (0.029 sec)
Finally, we're ready to run the TVF!
Running In the Database
- To see how to run the
power_of
UDF, please look here. - To see how to run the
split_str
TVF, please look here.
Running In the Database
Hooray, we can now run our Wasm function in the database!
When we imported our function, the database automatically converted the hyphen
s (-
) in our function name to underscores (_
). So, power-of
is now a UDF called power_of
.
Using the following syntax, we can run it as a UDF:
SELECT power_of(2, 8);
... which should return a single-column row with the value 256
.
Neat!
Let's review what we've learned.
Running In the Database
Hooray, we can now run our Wasm function in the database!
When we imported our function, the database automatically converted the hyphens (-
) in our function name to underscores (_
). So, split-str
is now a UDF called split_str
.
Using the following syntax, we can run our function as a TVF:
SELECT * FROM split_str('wasm_rocks_the_house', '_');
... which will produce the following output:
+-------+-----+
| str | idx |
+-------+-----+
| wasm | 0 |
| rocks | 5 |
| the | 11 |
| house | 15 |
+-------+-----+
94 rows in set (0.001 sec)
Awesome!
Let's review what we've learned.
Wrap-Up
Well, this concludes our little tutorial. In this tutorial, we learned how to turn some simple use cases into WebAssembly programs. We then examined how to test them, load them into SingleStoreDB, and run them there.
Hopefully, this helps you kickstart the creation of your own Wasm UDFs and TVFs. Thanks for tuning in!