From ffcb618b1eab4d768fd7f8aa85dff4188399b1f0 Mon Sep 17 00:00:00 2001 From: crwen <1543720935@qq.com> Date: Wed, 25 Dec 2024 17:53:04 +0800 Subject: [PATCH] docs: init user documentation --- .gitignore | 1 + guide/book.toml | 11 ++ guide/src/SUMMARY.md | 11 ++ guide/src/contribution/build.md | 3 + guide/src/contribution/index.md | 0 guide/src/examples/datafusion.md | 3 + guide/src/examples/declare.md | 102 +++++++++++++++++++ guide/src/examples/index.md | 1 + guide/src/examples/wasm.md | 72 ++++++++++++++ guide/src/introduction.md | 11 ++ guide/src/start.md | 166 +++++++++++++++++++++++++++++++ 11 files changed, 381 insertions(+) create mode 100644 guide/book.toml create mode 100644 guide/src/SUMMARY.md create mode 100644 guide/src/contribution/build.md create mode 100644 guide/src/contribution/index.md create mode 100644 guide/src/examples/datafusion.md create mode 100644 guide/src/examples/declare.md create mode 100644 guide/src/examples/index.md create mode 100644 guide/src/examples/wasm.md create mode 100644 guide/src/introduction.md create mode 100644 guide/src/start.md diff --git a/.gitignore b/.gitignore index d8d934f7..2815f2b3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ Cargo.lock db_path bindings/python/target +guide/book \ No newline at end of file diff --git a/guide/book.toml b/guide/book.toml new file mode 100644 index 00000000..98486e8f --- /dev/null +++ b/guide/book.toml @@ -0,0 +1,11 @@ +[book] +authors = ["crwen"] +language = "en" +multilingual = false +src = "src" +title = "The Tonbo Guide" + +[output.html] +git-repository-url = "https://github.com/tonbo-io/tonbo" +[output.html.playground] +runnable = false diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md new file mode 100644 index 00000000..90921bcf --- /dev/null +++ b/guide/src/SUMMARY.md @@ -0,0 +1,11 @@ +# Summary + +[Introduction](./introduction.md) + +- [Getting started](./start.md) +- [Examples](./examples/index.md) + - [Using Tonbo](./examples/declare.md) + - [Integrate with Datafusio](./examples/datafusion.md) + - [Using under Wasm](./examples/wasm.md) +- [Contribution](./contribution/index.md) + - [Building](./contribution/build.md) diff --git a/guide/src/contribution/build.md b/guide/src/contribution/build.md new file mode 100644 index 00000000..6fef9612 --- /dev/null +++ b/guide/src/contribution/build.md @@ -0,0 +1,3 @@ +# Building Tonbo + +TODO diff --git a/guide/src/contribution/index.md b/guide/src/contribution/index.md new file mode 100644 index 00000000..e69de29b diff --git a/guide/src/examples/datafusion.md b/guide/src/examples/datafusion.md new file mode 100644 index 00000000..33a9b924 --- /dev/null +++ b/guide/src/examples/datafusion.md @@ -0,0 +1,3 @@ +# Integrate with Datafusio + +TODO diff --git a/guide/src/examples/declare.md b/guide/src/examples/declare.md new file mode 100644 index 00000000..151c6366 --- /dev/null +++ b/guide/src/examples/declare.md @@ -0,0 +1,102 @@ +# Using Tonbo + +define your schema + +```rust +use tonbo::Record; + +/// Use macro to define schema of column family just like ORM +/// It provides type-safe read & write API +#[derive(Record, Debug)] +pub struct User { + #[record(primary_key)] + name: String, + email: Option, + age: u8, + bytes: Bytes, +} +``` + +```rust +use std::ops::Bound; + +use bytes::Bytes; +use fusio::path::Path; +use futures_util::stream::StreamExt; +use tokio::fs; +use tonbo::{executor::tokio::TokioExecutor, DbOption, Projection, Record, DB}; + + +#[tokio::main] +async fn main() { + // make sure the path exists + let _ = fs::create_dir_all("./db_path/users").await; + + let options = DbOption::new( + Path::from_filesystem_path("./db_path/users").unwrap(), + &UserSchema, + ); + // pluggable async runtime and I/O + let db = DB::new(options, TokioExecutor::current(), UserSchema) + .await + .unwrap(); + + // insert with owned value + db.insert(User { + name: "Alice".into(), + email: Some("alice@gmail.com".into()), + age: 22, + bytes: Bytes::from(vec![0, 1, 2]), + }) + .await + .unwrap(); + + { + // tonbo supports transaction + let txn = db.transaction().await; + + // get from primary key + let name = "Alice".into(); + + // get the zero-copy reference of record without any allocations. + let user = txn + .get( + &name, + // tonbo supports pushing down projection + Projection::All, + ) + .await + .unwrap(); + assert!(user.is_some()); + assert_eq!(user.unwrap().get().age, Some(22)); + + { + let upper = "Blob".into(); + // range scan of user + let mut scan = txn + .scan((Bound::Included(&name), Bound::Excluded(&upper))) + // tonbo supports pushing down projection + .projection(vec![1, 3]) + // push down limitation + .limit(1) + .take() + .await + .unwrap(); + while let Some(entry) = scan.next().await.transpose().unwrap() { + assert_eq!( + entry.value(), + Some(UserRef { + name: "Alice", + email: Some("alice@gmail.com"), + age: None, + bytes: Some(&[0, 1, 2]), + }) + ); + } + } + + // commit transaction + txn.commit().await.unwrap(); + } +} +``` diff --git a/guide/src/examples/index.md b/guide/src/examples/index.md new file mode 100644 index 00000000..e4a2ccd1 --- /dev/null +++ b/guide/src/examples/index.md @@ -0,0 +1 @@ +# Examples of using Tonbo diff --git a/guide/src/examples/wasm.md b/guide/src/examples/wasm.md new file mode 100644 index 00000000..12b405cc --- /dev/null +++ b/guide/src/examples/wasm.md @@ -0,0 +1,72 @@ + +# Using under Wasm + +This is the Wasm example of tonbo showing how to use tonbo under Wasm. + +## `Cargo.toml` + +Since only limited features of tokio can be used in wasm, we need to disable tokio and use `wasm` feature in tonbo. + +```toml +fusio = { git = "https://github.com/tonbo-io/fusio.git", rev = "216eb446fb0a0c6e5e85bfac51a6f6ed8e5ed606", package = "fusio", version = "0.3.3", features = [ + "dyn", + "fs", +] } +tonbo = { git = "https://github.com/tonbo-io/tonbo", default-features = false, features = ["wasm"] } +``` + +## Create DB + +Tonbo provide [OPFS(origin private file system)](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system) as storage backend, but the path is a little different. You should use `Path::from_opfs_path` or `Path::parse` rather than `Path::from_filesystem_path` and it is not permitted to use paths that temporarily step outside the sandbox with something like `../foo` or `./bar`. + +```rust +use fusio::path::Path; +use tonbo::{executor::opfs::OpfsExecutor, DbOption, DB}; + +async fn main() { + + let options = DbOption::new( + Path::from_opfs_path("db_path/users").unwrap(), + &UserSchema, + ); + let db = DB::::new(options, OpfsExecutor::new(), UserSchema) + .await + .unwrap(); +} +``` + +## Operations on DB + +After create `DB` instance, you can operate it as usual + +```rust +let txn = db.transaction().await; + +// get from primary key +let name = "Alice".into(); + +let user = txn.get(&name, Projection::All).await.unwrap(); + +let upper = "Blob".into(); +// range scan of user +let mut scan = txn + .scan((Bound::Included(&name), Bound::Excluded(&upper))) + // tonbo supports pushing down projection + .projection(vec![1]) + // push down limitation + .limit(1) + .take() + .await + .unwrap(); + +while let Some(entry) = scan.next().await.transpose().unwrap() { + assert_eq!( + entry.value(), + Some(UserRef { + name: "Alice", + email: Some("alice@gmail.com"), + age: None, + }) + ); +} +``` diff --git a/guide/src/introduction.md b/guide/src/introduction.md new file mode 100644 index 00000000..808c90f4 --- /dev/null +++ b/guide/src/introduction.md @@ -0,0 +1,11 @@ +# The Tonbo user guide +Welcome to the tonbo user guide! This book is about [tonbo](https://github.com/tonbo-io/tonbo). Tonbo is an embedded, persistent database offering fast KV-like methods for conveniently writing and scanning type-safe structured data. Tonbo can be used to build data-intensive applications, including other types of databases. + + +The rough order of material in this user guide is as follows: +1. Getting started +2. Examples on using tonbo +3. How to make contributions to Tonbo + + +If you want to learn the design of tonbo, you can see this [blog](https://tonbo.io/blog/introducing-tonbo). diff --git a/guide/src/start.md b/guide/src/start.md new file mode 100644 index 00000000..b1b62f32 --- /dev/null +++ b/guide/src/start.md @@ -0,0 +1,166 @@ +## Installation + +To get started using tonbo you should make sure you have Rust installed on your system. If you haven't alreadly done yet, try following the instructions [here](https://www.rust-lang.org/tools/install). + +## Adding dependencies + +```toml +fusio = { git = "https://github.com/tonbo-io/fusio.git", rev = "216eb446fb0a0c6e5e85bfac51a6f6ed8e5ed606", package = "fusio", version = "0.3.3", features = [ + "dyn", + "fs", +] } +tokio = { version = "1", features = ["full"] } +tonbo = { git = "https://github.com/tonbo-io/tonbo" } +``` + +## Defining Schema + +You can use `Record` macro to define schema of column family just like ORM. Tonbo will generate all relevant files for you at compile time. + +```rust +use tonbo::Record; + +#[derive(Record, Debug)] +pub struct User { + #[record(primary_key)] + name: String, + email: Option, + age: u8, + bytes: Bytes, +} +``` + +- `Record`: Declare this struct as a Tonbo Schema +- `#[record(primary_key)]`: Declare this key as primary key. Compound primary key is not supported now. +- `Option` type represents this field can be null, otherwise it can not be null. + +Now, Tonbo support these types: + +- Number type: `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64` +- Boolean type: `bool` +- String type: `bool` +- Bytes: `bytes::Bytes` + +## Create DB + +After define you schema, you can create `DB` with a customized `DbOption` + +```rust +use std::fs; +use fusio::path::Path; +use tonbo::{executor::tokio::TokioExecutor, DbOption, DB}; + +#[tokio::main] +async fn main() { + // make sure the path exists + fs::create_dir_all("./db_path/users").unwrap(); + + let options = DbOption::new( + Path::from_filesystem_path("./db_path/users").unwrap(), + &UserSchema, + ); + let db = DB::::new(options, TokioExecutor::current(), UserSchema) + .await + .unwrap(); +} +``` + +`UserSchema` is a struct that tonbo generates for you in the compile time, so you do not need to import it. + +## Read/Write data + +After create `DB`, you can execute `insert`, `remove`, `get` now. But remember that you will get a `UserRef` object rather than the `User`, if you get record from tonbo. This is a struct that tonbo generates for you in the compile time. + +```rust +db.insert(User { + name: "Alice".into(), + email: Some("alice@gmail.com".into()), + age: 22, +}) +.await +.unwrap(); + +let age = db + .get(&"Alice".into(), |entry| { + // entry.get() will get a `UserRef` + let user = entry.get(); + println!("{:#?}", user); + user.age + }) + .await + .unwrap(); +assert!(age.is_some()); +assert_eq!(age, Some(22)); +``` + +## Using transaction + +Tonbo supports transaction. You can also push down filter, limit and projection operators in query. + +```rust +let txn = db.transaction().await; + +// get from primary key +let name = "Alice".into(); + +// get the zero-copy reference of record without any allocations. +let user = txn.get(&name, Projection::All).await.unwrap(); + +let upper = "Blob".into(); +// range scan of user +let mut scan = txn + .scan((Bound::Included(&name), Bound::Excluded(&upper))) + // tonbo supports pushing down projection + .projection(vec![1]) + // push down limitation + .limit(1) + .take() + .await + .unwrap(); + +while let Some(entry) = scan.next().await.transpose().unwrap() { + assert_eq!( + entry.value(), + Some(UserRef { + name: "Alice", + email: Some("alice@gmail.com"), + age: None, + }) + ); +} +``` + +## Using S3 backends + +Tonbo supports various storage backends, such as OPFS, S3, and maybe more in the future. You can use `DbOption::level_path` to specify which backend to use. + +For local storage, you can use `FsOptions::Local` as the parameter. And you can use `FsOptions::S3` for S3 storage. After create `DB`, you can then operator it like normal. + +```rust +use fusio::{path::Path, remotes::aws::AwsCredential}; +use fusio_dispatch::FsOptions; +use tonbo::{executor::tokio::TokioExecutor, DbOption, DB}; + +#[tokio::main] +async fn main() { + let fs_option = FsOptions::S3 { + bucket: "wasm-data".to_string(), + credential: Some(AwsCredential { + key_id: "key_id".to_string(), + secret_key: "secret_key".to_string(), + token: None, + }), + endpoint: None, + sign_payload: None, + checksum: None, + region: Some("region".to_string()), + }; + + let options = DbOption::new(Path::from_filesystem_path("s3_path").unwrap(), &UserSchema) + .level_path(2, "l2", fs_option); + + let db = DB::::new(options, TokioExecutor::current(), UserSchema) + .await + .unwrap(); +} +```