Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions providers/provider-sqldb-libsql/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Rust build artifacts
target/

# Wash build artifacts
build/
*.par.gz

# libsql database files
data/
22 changes: 22 additions & 0 deletions providers/provider-sqldb-libsql/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "sqldb-libsql-provider"
version = "0.1.0"
edition = "2021"
description = """
wasmCloud SQL database provider for libSQL.
"""

[workspace]

[badges.maintenance]
status = "actively-developed"

[dependencies]
anyhow = "1"
deadpool-libsql = { version = "0.1.0" }
libsql = { version = "0.9" }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
wasmcloud-provider-sdk = { version = "0.13.0", features = ["otel"] }
wit-bindgen-wrpc = "0.9.0"
93 changes: 93 additions & 0 deletions providers/provider-sqldb-libsql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 🪶 SQL Database libSQL Provider

This capability provider implements the [`wasmcloud:sqldb-libsql`][wasmcloud-sqldb-libsql-wit] WIT package, which enables SQL-driven database interaction with a [libSQL][libsql] database cluster.

This provider handles concurrent component connections, and components which are linked to it should specify configuration at link time (see [the named configuration settings section](#-named-configuration-settings) for more details.

Want to read all the functionality included the interface? [Start from `provider.wit`][provider-wit] to read what this provider can do, and work your way to [`query.wit`][query-wit] and [`types.wit`][types-wit].

Note that connections are local to a single provider, so multiple providers running on the same lattice will _not_ share connections automatically.

[libsql]: https://docs.turso.tech/libsql
[wasmcloud-sqldb-libsql-wit]: https://github.com/jtakakura/wasmcloud-provider-sqldb-libsql/tree/main/wit
[provider-wit]: https://github.com/jtakakura/wasmcloud-provider-sqldb-libsql/blob/main/wit/provider.wit
[query-wit]: https://github.com/jtakakura/wasmcloud-provider-sqldb-libsql/blob/main/wit/query.wit
[types-wit]: https://github.com/jtakakura/wasmcloud-provider-sqldb-libsql/blob/main/wit/types.wit

## 📑 Named configuration Settings

As connection details are considered sensitive information, they should be specified via named configuration to the provider, and _specified_ via link definitions.
WADM files should not be checked into source control containing secrets.

New named configuration can be specified by using `wash config put`.

| Property | Example | Description |
| ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LIBSQL_URL` | `http://localhost:8080` | Remote libSQL Database URL |
| `LIBSQL_NAMESPACE` | `libsql` | Remote libSQL Database Namespace |
| `LIBSQL_POOL_SIZE` | `12` | Maximum size of the connection pool (configures [max_size](https://docs.rs/deadpool-libsql/0.1.0/deadpool_libsql/struct.PoolConfig.html#structfield.max_size)) |

Once named configuration with the keys above is created, it can be referenced as `target_config` for a link to this provider.

For example, the following WADM manifest fragment:

```yaml
- name: querier
type: component
properties:
image: file://./build/sqldb_libsql_query_s.wasm
traits:
- type: spreadscaler
properties:
instances: 1
- type: link
properties:
target: sqldb-libsql
namespace: wasmcloud
package: libsql
interfaces: [execute, query]
target_config:
- name: default-libsql
```

The `querier` component in the snippet above specifies a link to a `sqldb-libsql` target, with `target_config` that is only specifies `name` (no `properties`).

> [!WARNING]
> While `LIBSQL_AUTH_TOKEN` can be specified as named configuration, it should be specified as a secret.
>
> In a future version, this will be required.

## 🔐 Secret Settings

While most values can be specified via named configuration, sensitive values like the `LIBSQL_AUTH_TOKEN` should be specified via _secrets_.

New secrets be specified by using `wash secrets put`.

| Property | Example | Description |
| ------------------- | ---------- | ------------------------- |
| `LIBSQL_AUTH_TOKEN` | `xxxxx.yyyyy.zzzzz` | Remote libSQL Database Auth Token |

Once a secret has been created, it can be referenced in the link to the provider.

For example, the following WADM manifest fragment:

```yaml
- name: querier
type: component
properties:
image: file://./build/sqldb_libsql_query_s.wasm
traits:
- type: spreadscaler
properties:
instances: 1
- type: link
properties:
target: sqldb-libsql
namespace: wasmcloud
package: libsql
interfaces: [execute, query]
target_secrets:
- name: default-libsql-secrets
```

The `querier` component in the snippet above specifies a link to a `sqldb-libsql` target, with `target_config` that is only specifies `name` (no `properties`).
5 changes: 5 additions & 0 deletions providers/provider-sqldb-libsql/component/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Rust build artifacts
target/

# Wash build artifacts
build/
12 changes: 12 additions & 0 deletions providers/provider-sqldb-libsql/component/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "sqldb-postgres-query"
edition = "2021"
version = "0.1.0"

[workspace]

[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen = "0.43.0"
11 changes: 11 additions & 0 deletions providers/provider-sqldb-libsql/component/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Custom template test component

This component is meant to test the [custom template capability provider](../) by an implementation of the interface on the component.

## Build

Use `wash build` to build this component.

## Deploy

Use the [wadm.yaml](../wadm.yaml) in the parent directory to deploy this component alongside the provider.
93 changes: 93 additions & 0 deletions providers/provider-sqldb-libsql/component/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
wit_bindgen::generate!({ generate_all });

// NOTE: the imports below is generated by wit_bindgen::generate, due to the
// WIT definition(s) specified in `wit`
use wasmcloud::libsql::execute::{
execute, execute_batch, execute_transactional_batch, last_insert_rowid,
};
use wasmcloud::libsql::query::query;
use wasmcloud::libsql::types::LibsqlValue;

// NOTE: the `Guest` trait corresponds to the export of the `invoke` interface,
// namespaced to the current WIT namespace & package ("wasmcloud:examples")
use exports::wasmcloud::examples::invoke::Guest;

/// This struct must implement the all `export`ed functionality
/// in the WIT definition (see `wit/component.wit`)
struct QueryRunner;

const CREATE_TABLE_QUERY: &str = r#"
CREATE TABLE IF NOT EXISTS example (
id INTEGER PRIMARY KEY AUTOINCREMENT,
description text NOT NULL,
created_at TEXT NOT NULL DEFAULT (DATETIME('now', 'localtime'))
);
"#;

/// A basic insert query
const INSERT_QUERY: &str = r#"
INSERT INTO example (description) VALUES (?1);
"#;

/// A batch insert query, which inserts multiple rows in a single query
const INSERT_QUERIES_1: &str = r#"
INSERT INTO example (description) VALUES ("batch inserted example row#1");
INSERT INTO example (description) VALUES ("batch inserted example row#2");
INSERT INTO example (description) VALUES ("batch inserted example row#3");
"#;

const INSERT_QUERIES_2: &str = r#"
INSERT INTO example (description) VALUES ("batch inserted example row#4");
INSERT INTO example (description) VALUES ("batch inserted example row#5");
INSERT INTO example (description) VALUES ("batch inserted example row#6");
"#;

/// A SELECT query, which takes the ID insert query, using Postgres `RETURNING` syntax,
/// which returns the contents of the row that was inserted
const SELECT_QUERY: &str = r#"
SELECT * FROM example WHERE id = ?1;
"#;

impl Guest for QueryRunner {
fn call() -> String {
// First, ensure the right table is present
if let Err(e) = execute(CREATE_TABLE_QUERY, &[]) {
return format!("ERROR: failed to create table: {e}");
};

// Insert multiple rows in a single batch
if let Err(e) = execute_batch(INSERT_QUERIES_1) {
return format!("ERROR: failed to batch insert rows: {e}");
};

// Insert multiple rows in a single transactional batch
if let Err(e) = execute_transactional_batch(INSERT_QUERIES_2) {
return format!("ERROR: failed to batch insert rows: {e}");
};

// Insert a row into the example
if let Err(e) = execute(
INSERT_QUERY,
&[LibsqlValue::Text("inserted example row!".into())],
) {
return format!("ERROR: failed to insert row: {e}");
};

// Get the last insert row ID
let last_insert_rowid = match last_insert_rowid() {
Ok(id) => id,
Err(e) => return format!("ERROR: failed to get last insert row ID: {e}"),
};

// Do an explicit SELECT for the row we just inserted, using the ID that was returned
//
// NOTE: normally you would not need this SELECT, thanks to RETURNING:
// https://www.postgresql.org/docs/current/dml-returning.html
match query(SELECT_QUERY, &[LibsqlValue::Integer(last_insert_rowid)]) {
Ok(rows) => format!("SUCCESS: inserted and manually retrieved new row:\n{rows:#?}"),
Err(e) => format!("ERROR: failed to retrieve inserted row: {e}"),
}
}
}

export!(QueryRunner);
4 changes: 4 additions & 0 deletions providers/provider-sqldb-libsql/component/wasmcloud.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This file is automatically generated.
# It is not intended for manual editing.
version = 1
packages = []
5 changes: 5 additions & 0 deletions providers/provider-sqldb-libsql/component/wasmcloud.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name = "Custom template test component"
language = "rust"
type = "component"

[component]
4 changes: 4 additions & 0 deletions providers/provider-sqldb-libsql/component/wit/deps.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[libsql]
path = "../../wit"
sha256 = "d1be02228dfd4544135590c8bf8b2bf9d62f99a1c0fda601a90c23831d8355f9"
sha512 = "72ee6d35767d1cb385f1453454cd0aa5bc20c6977c78555b7c8fb124f0379bace608b1ed8de51568903c80f0a10b023a3695ffbf8d429c6d115c2e1191008910"
1 change: 1 addition & 0 deletions providers/provider-sqldb-libsql/component/wit/deps.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libsql = { path = "../../wit" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package wasmcloud:libsql@0.1.0-draft;

world provider-sqldb-libsql {
export execute;
export query;
export types;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package wasmcloud:libsql@0.1.0-draft;

/// Interface for executing queries against a libSQL database
interface execute {
use types.{libsql-value, result-row, query-error};

/// Execute a query that does not return rows, such as an `INSERT`, `UPDATE`, or `DELETE`
///
/// Queries *must* be parameterized, with named arguments in the form of `?<integer>`, for example:
///
/// ```
/// INSERT INTO users (email, username) VALUES (?1, ?2);
/// ```
execute: func(query: string, params: list<libsql-value>) -> result<u64, query-error>;

/// Execute a batch set of statements
///
execute-batch: func(query: string) -> result<list<result-row>, query-error>;

/// Execute a batch set of statements atomically in a transaction
///
execute-transactional-batch: func(query: string) -> result<list<result-row>, query-error>;

/// Get the last inserted row ID, if the last query was an `INSERT`
///
last-insert-rowid: func() -> result<s64, query-error>;
}

/// Interface for querying a libSQL database
interface query {
use types.{libsql-value, result-row, query-error};

/// Query a libSQL database, leaving connection/session management
/// to the callee/implementer of this interface (normally a provider configured with connection credentials)
///
/// Queries *must* be parameterized, with named arguments in the form of `?<integer>`, for example:
///
/// ```
/// SELECT email,username FROM users WHERE id=?1;
/// ```
///
query: func(query: string, params: list<libsql-value>) -> result<list<result-row>, query-error>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package wasmcloud:libsql@0.1.0-draft;

/// Types used by components and providers of a SQLDB libSQL interface
interface types {

/// Errors that occur while executing queries
variant query-error {
/// A completely unexpected error, specific to executing queries
unexpected(string),
}

/// libSQL data values, usable as parameters or via queries
/// see: https://www.sqlite.org/datatype3.html
///
variant libsql-value {
null,

// Numeric
integer(s64),
real(f64),

// String
text(string),

// Binary
blob(list<u8>),
}

record result-row-entry {
/// Name of the result column
column-name: string,
/// Value of the result column
value: libsql-value,
}

type result-row = list<result-row-entry>;
}
15 changes: 15 additions & 0 deletions providers/provider-sqldb-libsql/component/wit/world.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package wasmcloud:examples;

/// Invoke a component and receive string output. Similar to wasi:cli/command.run, without args
///
/// This enables the component to be used with `wash call`
interface invoke {
/// Invoke a component
call: func() -> string;
}

world component {
import wasmcloud:libsql/execute@0.1.0-draft;
import wasmcloud:libsql/query@0.1.0-draft;
export invoke;
}
11 changes: 11 additions & 0 deletions providers/provider-sqldb-libsql/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
libsql:
image: ghcr.io/tursodatabase/libsql-server:latest
platform: linux/amd64
ports:
- "8080:8080"
- "5001:5001"
environment:
- SQLD_NODE=primary
volumes:
- ./data/libsql:/var/lib/sqld
Loading