Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: init user documentation #254

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
Cargo.lock
db_path
bindings/python/target
guide/book
11 changes: 11 additions & 0 deletions guide/book.toml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions guide/src/contribution/build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Building Tonbo

TODO
Empty file added guide/src/contribution/index.md
Empty file.
3 changes: 3 additions & 0 deletions guide/src/examples/datafusion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Integrate with Datafusio

TODO
102 changes: 102 additions & 0 deletions guide/src/examples/declare.md
Original file line number Diff line number Diff line change
@@ -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<String>,
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("[email protected]".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("[email protected]"),
age: None,
bytes: Some(&[0, 1, 2]),
})
);
}
}

// commit transaction
txn.commit().await.unwrap();
}
}
```
1 change: 1 addition & 0 deletions guide/src/examples/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Examples of using Tonbo
72 changes: 72 additions & 0 deletions guide/src/examples/wasm.md
Original file line number Diff line number Diff line change
@@ -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::<User, OpfsExecutor>::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("[email protected]"),
age: None,
})
);
}
```
11 changes: 11 additions & 0 deletions guide/src/introduction.md
Original file line number Diff line number Diff line change
@@ -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).
166 changes: 166 additions & 0 deletions guide/src/start.md
Original file line number Diff line number Diff line change
@@ -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<String>,
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::<User, TokioExecutor>::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("[email protected]".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("[email protected]"),
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::<User, TokioExecutor>::new(options, TokioExecutor::current(), UserSchema)
.await
.unwrap();
}
```
Loading