From 567a2a58569814488240e7bd71fc7bd384983df6 Mon Sep 17 00:00:00 2001 From: Rushmore Mushambi Date: Tue, 13 Feb 2024 16:22:11 +0200 Subject: [PATCH] Improve `Surreal` documentation (#3506) Co-authored-by: Obinna Ekwuno --- Makefile.local.toml | 2 +- lib/src/api/engine/any/mod.rs | 199 ++++++++++++++++++++------------ lib/src/api/engine/local/mod.rs | 20 ++-- 3 files changed, 136 insertions(+), 85 deletions(-) diff --git a/Makefile.local.toml b/Makefile.local.toml index 934417ac..77bd3bd0 100644 --- a/Makefile.local.toml +++ b/Makefile.local.toml @@ -17,7 +17,7 @@ dependencies = ["cargo-upgrade", "cargo-update"] [tasks.docs] category = "LOCAL USAGE" command = "cargo" -env = { RUSTFLAGS = "--cfg surrealdb_unstable" } +env = { RUSTDOCFLAGS = "--cfg surrealdb_unstable" } args = ["doc", "--open", "--no-deps", "--package", "surrealdb", "--features", "rustls,native-tls,protocol-ws,protocol-http,kv-mem,kv-speedb,kv-rocksdb,kv-tikv,http,scripting,jwks"] # Test diff --git a/lib/src/api/engine/any/mod.rs b/lib/src/api/engine/any/mod.rs index c5bbc546..e07cd307 100644 --- a/lib/src/api/engine/any/mod.rs +++ b/lib/src/api/engine/any/mod.rs @@ -1,87 +1,132 @@ //! Dynamic support for any engine //! +//! SurrealDB supports various ways of storing and accessing your data. For storing data we support a number of +//! key value stores. These are RocksDB, SpeeDB, TiKV, FoundationDB and an in-memory store. We call these +//! local engines. RocksDB and SpeeDB are file-based, single node key value stores. TiKV and FoundationDB are +//! are distributed stores that can scale horizontally across multiple nodes. The in-memory store does not persist +//! your data, it only stores it in memory. All these can be embedded in your application, so you don't need to +//! spin up a SurrealDB server first in order to use them. We also support spinning up a server externally and then +//! access your database via WebSockets or HTTP. We call these remote engines. +//! +//! The Rust SDK abstracts away the implementation details of the engines to make them work in a unified way. +//! All these engines, whether they are local or remote, work exactly the same way using the same API. The only +//! difference in the API is the endpoint you use to access the engine. Normally you provide the scheme of the engine +//! you want to use as a type parameter to `Surreal::new`. This allows you detect, at compile, whether the engine +//! you are trying to use is enabled. If not, your code won't compile. This is awesome but it strongly couples your +//! application to the engine you are using. In order to change an engine you would need to update your code to +//! the new scheme and endpoint you need to use and recompile it. This is where the `any` engine comes in. We will +//! call it `Surreal` (the type it creates) to avoid confusion with the word any. +//! +//! `Surreal` allows you to use any engine as long as it was enabled when compiling. Unlike with the typed scheme, +//! the choice of the engine is made at runtime depending on the endpoint that you provide as a string. If you use an +//! environment variable to provide this endpoint string, you won't need to change your code in order to +//! switch engines. The downside to this is that you will get a runtime error if you forget to enable the engine you +//! want to use when compiling your code. On the other hand, this totally decouples your application from the engine +//! you are using and makes it possible to use whichever engine SurrealDB supports by simply changing the Cargo +//! features you enable when compiling. This enables some cool workflows. +//! +//! One of the common use cases we see is using SurrealDB as an embedded database using RocksDB as the local engine. +//! This is a nice way to boost the performance of your application when all you need is a single node. The downside +//! of this approach is that RocksDB is not written in Rust so you will need to install some external dependencies +//! on your development machine in order to successfully compile it. Some of our users have reported that +//! this is not exactly straight-forward on Windows. Another issue is that RocksDB is very resource intensive to +//! compile and it takes a long time. Both of these issues can be easily avoided by using `Surreal`. You can +//! develop using an in-memory engine but deploy using RocksDB. If you develop on Windows but deploy to Linux then +//! you completely avoid having to build RocksDB on Windows at all. +//! +//! # Getting Started +//! +//! You can start by declaring your `surrealdb` dependency like this in Cargo.toml +//! +//! ```toml +//! surrealdb = { +//! version = "1", +//! +//! # Disables the default features, which are `protocol-ws` and `rustls`. +//! # Not necessary but can reduce your compile times if you don't need those features. +//! default-features = false, +//! +//! # Unconditionally enables the in-memory store. +//! # Also not necessary but this will make `cargo run` just work. +//! # Without it, you would need `cargo run --features surrealdb/kv-mem` during development. If you use a build +//! # tool like `make` or `cargo make`, however, you can put that in your build step and avoid typing it manually. +//! features = ["kv-mem"], +//! +//! # Also not necessary but this makes it easy to switch between `stable`, `beta` and `nightly` crates, if need be. +//! # See https://surrealdb.com/blog/introducing-nightly-and-beta-rust-crates for more information on those crates. +//! package = "surrealdb" +//! } +//! ``` +//! +//! You then simply need to instantiate `Surreal` instead of `Surreal` or `Surreal`. +//! //! # Examples //! -//! ```no_run -//! use serde::{Serialize, Deserialize}; -//! use serde_json::json; -//! use std::borrow::Cow; -//! use surrealdb::sql; -//! use surrealdb::engine::any::connect; -//! use surrealdb::opt::auth::Root; -//! -//! #[derive(Serialize, Deserialize)] -//! struct Name { -//! first: Cow<'static, str>, -//! last: Cow<'static, str>, -//! } -//! -//! #[derive(Serialize, Deserialize)] -//! struct Person { -//! title: Cow<'static, str>, -//! name: Name, -//! marketing: bool, -//! } +//! ```rust +//! use std::env; +//! use surrealdb::engine::any; +//! use surrealdb::engine::any::Any; +//! use surrealdb::opt::Resource; +//! use surrealdb::Surreal; //! //! #[tokio::main] -//! async fn main() -> surrealdb::Result<()> { -//! let db = connect("ws://localhost:8000").await?; +//! async fn main() -> Result<(), Box> { +//! // Use the endpoint specified in the environment variable or default to `memory`. +//! // This makes it possible to use the memory engine during development but switch it +//! // to any other engine for deployment. +//! let endpoint = env::var("SURREALDB_ENDPOINT").unwrap_or_else(|_| "memory".to_owned()); //! -//! // Signin as a namespace, database, or root user -//! db.signin(Root { -//! username: "root", -//! password: "root", -//! }).await?; +//! // Create the Surreal instance. This will create `Surreal`. +//! let db = any::connect(endpoint).await?; //! -//! // Select a specific namespace / database +//! // Specify the namespace and database to use //! db.use_ns("namespace").use_db("database").await?; //! -//! // Create a new person with a random ID -//! let created: Vec = db.create("person") -//! .content(Person { -//! title: "Founder & CEO".into(), -//! name: Name { -//! first: "Tobie".into(), -//! last: "Morgan Hitchcock".into(), -//! }, -//! marketing: true, -//! }) -//! .await?; -//! -//! // Create a new person with a specific ID -//! let created: Option = db.create(("person", "jaime")) -//! .content(Person { -//! title: "Founder & COO".into(), -//! name: Name { -//! first: "Jaime".into(), -//! last: "Morgan Hitchcock".into(), -//! }, -//! marketing: false, -//! }) -//! .await?; -//! -//! // Update a person record with a specific ID -//! let updated: Option = db.update(("person", "jaime")) -//! .merge(json!({"marketing": true})) -//! .await?; -//! -//! // Select all people records -//! let people: Vec = db.select("person").await?; -//! -//! // Perform a custom advanced query -//! let sql = r#" -//! SELECT marketing, count() -//! FROM type::table($table) -//! GROUP BY marketing -//! "#; -//! -//! let groups = db.query(sql) -//! .bind(("table", "person")) -//! .await?; +//! // Use the database like you normally would. +//! delete_user(&db, "jane").await?; //! //! Ok(()) //! } +//! +//! // Deletes a user from the user table in the database +//! async fn delete_user(db: &Surreal, username: &str) -> surrealdb::Result<()> { +//! db.delete(Resource::from(("user", username))).await?; +//! Ok(()) +//! } //! ``` +//! +//! By doing something like this, you can use an in-memory database on your development machine and you can just run `cargo run` +//! without having to specify the environment variable first or spinning up an external server remotely to avoid RocksDB's +//! compilation cost. You also don't need to install any `C` or `C++` dependencies on your Windows machine. For the production +//! binary you simply need to build it using something like +//! +//! ```bash +//! cargo build --features surrealdb/kv-rocksdb --release +//! ``` +//! +//! and export the `SURREALDB_ENDPOINT` environment variable when starting it. +//! +//! ```bash +//! export SURREALDB_ENDPOINT="rocksdb:/path/to/database/folder" +//! /path/to/binary +//! ``` +//! +//! The example above shows how you can avoid compiling RocksDB on your development machine, thereby avoiding dependency hell +//! and paying the compilation cost during development. This is not the only benefit you can derive from using `Surreal` +//! though. It's still useful even when your engine isn't expensive to compile. For example, the remote engines use pure Rust +//! dependencies but you can still benefit from using `Surreal` by using the in-memory engine for development and deploy +//! using a remote engine like the WebSocket engine. This way you avoid having to spin up a SurrealDB server first when +//! developing and testing your application. +//! +//! For some applications where you allow users to determine the engine they want to use, you can enable multiple engines for +//! them when building, or even enable them all. To do this you simply need to comma separate the Cargo features. +//! +//! ```bash +//! cargo build --features surrealdb/protocol-ws,surrealdb/kv-rocksdb,surrealdb/kv-tikv --release +//! ``` +//! +//! In this case, the binary you build will have support for accessing an external server via WebSockets, embedding the database +//! using RocksDB or using a distributed TiKV cluster. #[cfg(not(target_arch = "wasm32"))] mod native; @@ -224,17 +269,23 @@ impl Surreal { /// // Instantiate an in-memory instance /// let db = connect("mem://").await?; /// -/// // Instantiate an file-backed instance -/// let db = connect("file://temp.db").await?; +/// // Instantiate an file-backed instance (currently uses RocksDB) +/// let db = connect("file://path/to/database-folder").await?; +/// +/// /// // Instantiate an RocksDB-backed instance +/// let db = connect("rocksdb://path/to/database-folder").await?; +/// +/// // Instantiate an SpeeDB-backed instance +/// let db = connect("speedb://path/to/database-folder").await?; /// /// // Instantiate an IndxDB-backed instance -/// let db = connect("indxdb://MyDatabase").await?; +/// let db = connect("indxdb://DatabaseName").await?; /// /// // Instantiate a TiKV-backed instance /// let db = connect("tikv://localhost:2379").await?; /// /// // Instantiate a FoundationDB-backed instance -/// let db = connect("fdb://fdb.cluster").await?; +/// let db = connect("fdb://path/to/fdb.cluster").await?; /// # Ok(()) /// # } /// ``` diff --git a/lib/src/api/engine/local/mod.rs b/lib/src/api/engine/local/mod.rs index f4dcfef4..264c0529 100644 --- a/lib/src/api/engine/local/mod.rs +++ b/lib/src/api/engine/local/mod.rs @@ -168,7 +168,7 @@ pub struct Mem; /// use surrealdb::Surreal; /// use surrealdb::engine::local::File; /// -/// let db = Surreal::new::("temp.db").await?; +/// let db = Surreal::new::("path/to/database-folder").await?; /// # Ok(()) /// # } /// ``` @@ -183,7 +183,7 @@ pub struct Mem; /// use surrealdb::engine::local::File; /// /// let config = Config::default().strict(); -/// let db = Surreal::new::(("temp.db", config)).await?; +/// let db = Surreal::new::(("path/to/database-folder", config)).await?; /// # Ok(()) /// # } /// ``` @@ -204,7 +204,7 @@ pub struct File; /// use surrealdb::Surreal; /// use surrealdb::engine::local::RocksDb; /// -/// let db = Surreal::new::("temp.db").await?; +/// let db = Surreal::new::("path/to/database-folder").await?; /// # Ok(()) /// # } /// ``` @@ -219,7 +219,7 @@ pub struct File; /// use surrealdb::engine::local::RocksDb; /// /// let config = Config::default().strict(); -/// let db = Surreal::new::(("temp.db", config)).await?; +/// let db = Surreal::new::(("path/to/database-folder", config)).await?; /// # Ok(()) /// # } /// ``` @@ -240,7 +240,7 @@ pub struct RocksDb; /// use surrealdb::Surreal; /// use surrealdb::engine::local::SpeeDb; /// -/// let db = Surreal::new::("temp.db").await?; +/// let db = Surreal::new::("path/to/database-folder").await?; /// # Ok(()) /// # } /// ``` @@ -255,7 +255,7 @@ pub struct RocksDb; /// use surrealdb::engine::local::SpeeDb; /// /// let config = Config::default().strict(); -/// let db = Surreal::new::(("temp.db", config)).await?; +/// let db = Surreal::new::(("path/to/database-folder", config)).await?; /// # Ok(()) /// # } /// ``` @@ -276,7 +276,7 @@ pub struct SpeeDb; /// use surrealdb::Surreal; /// use surrealdb::engine::local::IndxDb; /// -/// let db = Surreal::new::("MyDatabase").await?; +/// let db = Surreal::new::("DatabaseName").await?; /// # Ok(()) /// # } /// ``` @@ -291,7 +291,7 @@ pub struct SpeeDb; /// use surrealdb::engine::local::IndxDb; /// /// let config = Config::default().strict(); -/// let db = Surreal::new::(("MyDatabase", config)).await?; +/// let db = Surreal::new::(("DatabaseName", config)).await?; /// # Ok(()) /// # } /// ``` @@ -348,7 +348,7 @@ pub struct TiKv; /// use surrealdb::Surreal; /// use surrealdb::engine::local::FDb; /// -/// let db = Surreal::new::("fdb.cluster").await?; +/// let db = Surreal::new::("path/to/fdb.cluster").await?; /// # Ok(()) /// # } /// ``` @@ -363,7 +363,7 @@ pub struct TiKv; /// use surrealdb::engine::local::FDb; /// /// let config = Config::default().strict(); -/// let db = Surreal::new::(("fdb.cluster", config)).await?; +/// let db = Surreal::new::(("path/to/fdb.cluster", config)).await?; /// # Ok(()) /// # } /// ```