Skip to content

Commit

Permalink
Add database clients
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Yuan <[email protected]>
  • Loading branch information
juntao committed Jul 18, 2024
1 parent 0ea42d3 commit 1b22873
Show file tree
Hide file tree
Showing 3 changed files with 303 additions and 12 deletions.
132 changes: 126 additions & 6 deletions docs/develop/rust/database/postgres_driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ A database connection is necessary for today's enterprise development. WasmEdge

<!-- prettier-ignore -->
:::note
Before we start, make sure [you have Rust and WasmEdge installed](../setup.md).
Before we start, [you need to have Rust and WasmEdge installed](../setup.md).
Make sure that you read the [special notes on networking apps](../setup#special-notes) especially if you are compiling Rust programs on a Mac.
:::

## Run the example
Expand All @@ -20,15 +21,134 @@ git clone https://github.com/WasmEdge/wasmedge-db-examples
cd wasmedge-db-examples/postgres

# Compile the rust code into WASM
cargo build --target wasm32-wasi --release
RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release

# Execute SQL statements against a PostgreSQL database at postgres://user:passwd@localhost/testdb
wasmedge --env "DATABASE_URL=postgres://user:passwd@localhost/testdb" target/wasm32-wasi/release/crud.wasm
```

## Configuration

In order to compile the `tokio-postgres` and `tokio` crates, we will need to apply patches to add WasmEdge-specific socket APIs to those crates in `Cargo.toml`.

```
[patch.crates-io]
tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" }
socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" }
tokio-postgres = { git = "https://github.com/second-state/rust-postgres.git" }
[dependencies]
tokio-postgres = "0.7"
tokio = { version = "1", features = [
"io-util",
"fs",
"net",
"time",
"rt",
"macros",
] }
```

## Code explanation

<!-- prettier-ignore -->
:::info
Work in Progress
:::
We first use a Rust struct to represent the database table.

```rust
#[derive(Debug)]
struct Order {
order_id: i32,
production_id: i32,
quantity: i32,
amount: f32,
shipping: f32,
tax: f32,
shipping_address: String,
}

impl Order {
fn new(
order_id: i32,
production_id: i32,
quantity: i32,
amount: f32,
shipping: f32,
tax: f32,
shipping_address: String,
) -> Self {
Self {
order_id,
production_id,
quantity,
amount,
shipping,
tax,
shipping_address,
}
}
}
```

Then, you can use the `tokio-postgres` API to access the database through its connection URL.
The code below shows how to perform basic CRUD operations using SQL commands.

```rust
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Error> {
// Connect to the database.
let (client, connection) = tokio_postgres::connect(&*get_url(), NoTls).await?;

// The connection object performs the actual communication with the database,
// so spawn it off to run on its own.
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});

client.execute("CREATE TABLE IF NOT EXISTS orders (order_id INT, production_id INT, quantity INT, amount REAL, shipping REAL, tax REAL, shipping_address VARCHAR(256));", &[]).await?;

let orders = vec![
Order::new(1, 12, 2, 56.0, 15.0, 2.0, String::from("Mataderos 2312")),
Order::new(2, 15, 3, 256.0, 30.0, 16.0, String::from("1234 NW Bobcat")),
Order::new(3, 11, 5, 536.0, 50.0, 24.0, String::from("20 Havelock")),
Order::new(4, 8, 8, 126.0, 20.0, 12.0, String::from("224 Pandan Loop")),
Order::new(5, 24, 1, 46.0, 10.0, 2.0, String::from("No.10 Jalan Besar")),
];

for order in orders.iter() {
client.execute(
"INSERT INTO orders (order_id, production_id, quantity, amount, shipping, tax, shipping_address) VALUES ($1, $2, $3, $4, $5, $6, $7)",
&[&order.order_id, &order.production_id, &order.quantity, &order.amount, &order.shipping, &order.tax, &order.shipping_address]
).await?;
}

let rows = client.query("SELECT * FROM orders;", &[]).await?;
for row in rows.iter() {
let order_id : i32 = row.get(0);
println!("order_id {}", order_id);

let production_id : i32 = row.get(1);
println!("production_id {}", production_id);

let quantity : i32 = row.get(2);
println!("quantity {}", quantity);

let amount : f32 = row.get(3);
println!("amount {}", amount);

let shipping : f32 = row.get(4);
println!("shipping {}", shipping);

let tax : f32 = row.get(5);
println!("tax {}", tax);

let shipping_address : &str = row.get(6);
println!("shipping_address {}", shipping_address);
}

client.execute("DELETE FROM orders;", &[]).await?;

Ok(())
}
```

132 changes: 132 additions & 0 deletions docs/develop/rust/database/qdrant_driver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
sidebar_position: 4
---

# Qdrant driver

WasmEdge is emerging as a lightweight, portable, and embeddable runtime for large language models (LLMs). LLM inference applications, such as RAG chatbots and AI agents, can be developed on Mac or Windows, compiled to Wasm once, and then deployed across Nvidia / AMD / ARM-powered devices or servers, fully taking advantage of on-device GPUs, NPUs, and accelerators.

Hence, besides the LLM inference runtime, those LLM applications also need to manage embeddings in vector databases. The [qdrant-rest-client](https://crates.io/crates/qdrant_rest_client) crate allows you to access the Qdrant vector database from your portable Wasm apps!

<!-- prettier-ignore -->
:::note
Before we start, [you need to have Rust and WasmEdge installed](../setup.md).
Make sure that you read the [special notes on networking apps](../setup#special-notes) especially if you are compiling Rust programs on a Mac.
:::

## Run the example

The [wasmedge-db-example/qdrant](https://github.com/WasmEdge/wasmedge-db-examples/tree/main/qdrant) is a Qdrant client example written in Rust.

```bash
git clone https://github.com/WasmEdge/wasmedge-db-examples
cd wasmedge-db-examples/qdrant

# Compile the rust code into WASM
RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release

# Perform vector data operations against a Qdrant at http://localhost:6333
wasmedge target/wasm32-wasi/release/qdrant_examples.wasm
```

## Configuration

In order to compile the `qdrant_rest_client` and `tokio` crates, we will need to apply patches to add WasmEdge-specific socket APIs to those crates in `Cargo.toml`.

```rust
[patch.crates-io]
socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" }
reqwest = { git = "https://github.com/second-state/wasi_reqwest.git", branch = "0.11.x" }
hyper = { git = "https://github.com/second-state/wasi_hyper.git", branch = "v0.14.x" }
tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" }

[dependencies]
anyhow = "1.0"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
url = "2.3"
tokio = { version = "1", features = ["io-util", "fs", "net", "time", "rt", "macros"] }
qdrant_rest_client = "0.1.0"
```

## Code explanation

The following program uses the `qdrant_rest_client` crate to access local Qdrant server through its RESTful API.
It first creates several points (vectors), saves those vectors to the Qdrant database, retrieves some vectors,
searches for vectors, and finally deletes them from the database.

```rust
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = qdrant::Qdrant::new();
// Create a collection with 10-dimensional vectors
let r = client.create_collection("my_test", 4).await;
println!("Create collection result is {:?}", r);

let mut points = Vec::<Point>::new();
points.push(Point {
id: PointId::Num(1),
vector: vec![0.05, 0.61, 0.76, 0.74],
payload: json!({"city": "Berlin"}).as_object().map(|m| m.to_owned()),
});
points.push(Point {
id: PointId::Num(2),
vector: vec![0.19, 0.81, 0.75, 0.11],
payload: json!({"city": "London"}).as_object().map(|m| m.to_owned()),
});
points.push(Point {
id: PointId::Num(3),
vector: vec![0.36, 0.55, 0.47, 0.94],
payload: json!({"city": "Moscow"}).as_object().map(|m| m.to_owned()),
});
points.push(Point {
id: PointId::Num(4),
vector: vec![0.18, 0.01, 0.85, 0.80],
payload: json!({"city": "New York"})
.as_object()
.map(|m| m.to_owned()),
});
points.push(Point {
id: PointId::Num(5),
vector: vec![0.24, 0.18, 0.22, 0.44],
payload: json!({"city": "Beijing"}).as_object().map(|m| m.to_owned()),
});
points.push(Point {
id: PointId::Num(6),
vector: vec![0.35, 0.08, 0.11, 0.44],
payload: json!({"city": "Mumbai"}).as_object().map(|m| m.to_owned()),
});

let r = client.upsert_points("my_test", points).await;
println!("Upsert points result is {:?}", r);

println!(
"The collection size is {}",
client.collection_info("my_test").await
);

let p = client.get_point("my_test", 2).await;
println!("The second point is {:?}", p);

let ps = client.get_points("my_test", vec![1, 2, 3, 4, 5, 6]).await;
println!("The 1-6 points are {:?}", ps);

let q = vec![0.2, 0.1, 0.9, 0.7];
let r = client.search_points("my_test", q, 2, None).await;
println!("Search result points are {:?}", r);

let r = client.delete_points("my_test", vec![1, 4]).await;
println!("Delete points result is {:?}", r);

println!(
"The collection size is {}",
client.collection_info("my_test").await
);

let q = vec![0.2, 0.1, 0.9, 0.7];
let r = client.search_points("my_test", q, 2, None).await;
println!("Search result points are {:?}", r);
Ok(())
}
```

51 changes: 45 additions & 6 deletions docs/develop/rust/database/redis_driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ WasmEdge provides a Redis driver for Rust developers, enabling developers to bui

<!-- prettier-ignore -->
:::note
Before we start, ensure [you have Rust and WasmEdge installed](../setup.md).
Before we start, [you need to have Rust and WasmEdge installed](../setup.md).
Make sure that you read the [special notes on networking apps](../setup#special-notes) especially if you are compiling Rust programs on a Mac.
:::

## Run the example
Expand All @@ -20,15 +21,53 @@ git clone https://github.com/WasmEdge/wasmedge-db-examples
cd wasmedge-db-examples/redis

# Compile the rust code into WASM
cargo build --target wasm32-wasi --release
RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release

# Execute Redis command against a Redis instance at redis://localhost/
wasmedge --env "REDIS_URL=redis://localhost/" target/wasm32-wasi/release/wasmedge-redis-client-examples.wasm
```

## Configuration

In order to compile the `redis` and `tokio` crates, we will need to apply patches to add WasmEdge-specific socket APIs to those crates in `Cargo.toml`.

```rust
[patch.crates-io]
tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" }

[dependencies]
anyhow = "1.0"
chrono = { version = "0.4", features = ["serde"] }
tokio = { version = "1", features = ["full"] }
redis = { version = "0.25.4", default-features = false, features = [
"tokio-comp",
] }
```

## Code explanation

<!-- prettier-ignore -->
:::info
Work in Progress
:::
The following program uses the `redis` crate to access a Redis server through its connection URL.
It gets the current time, saves the timestamp object to the Redis server, and then reads it back for
display on the console.

```rust
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
// connect to redis
let client = redis::Client::open(&*get_url()).unwrap();
let mut con = client.get_multiplexed_async_connection().await.unwrap();

let time = format!("{}", chrono::Utc::now());
// throw away the result, just make sure it does not fail
let _: () = con.set("current_time", time).await.unwrap();

// read back the key and return it. Because the return value
// from the function is a result for String, this will automatically
// convert into one.
let value: String = con.get("current_time").await.unwrap();
println!("Successfully GET `time`: {}", value);

Ok(())
}
```

0 comments on commit 1b22873

Please sign in to comment.