This is an implementation of a simple phonebook for the command line that uses an embedded SurrealDB databse with a RocksDb storage using SQL.
It seems that there are so many ways to use SurrealDB that we need that many words to describe the particular implementation we have.
SurrealDB can be used either as a stand-along database server
examples/surrealdb/cli-phone-book-with-embedded-rocksdb/Cargo.toml
[package]
name = "phonebook"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0.193", features = ["derive"] }
surrealdb = { version = "1.0.2", features = ["kv-rocksdb"] }
tempdir = "0.3.7"
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] }
examples/surrealdb/cli-phone-book-with-embedded-rocksdb/src/main.rs
use serde::{Deserialize, Serialize};
use surrealdb::engine::local::RocksDb;
use surrealdb::Surreal;
#[derive(Debug, Deserialize, Serialize)]
struct Entry {
name: String,
phone: String,
}
#[tokio::main]
async fn main() -> surrealdb::Result<()> {
let args = std::env::args().collect::<Vec<String>>();
let database_folder = match std::env::var("DATABASE_PATH") {
Ok(val) => std::path::PathBuf::from(val),
Err(_) => {
let current_dir = std::env::current_dir().unwrap();
current_dir.join("phonebook.db")
}
};
let db = Surreal::new::<RocksDb>(database_folder).await?;
db.use_ns("test").use_db("test").await?;
// Maybe do this only when we create the database
let _response = db
.query("DEFINE INDEX entry_email ON TABLE entry COLUMNS name UNIQUE")
.await?;
if args.len() == 4 {
let command = &args[1];
if command == "add" {
let name = &args[2];
let phone = &args[3];
let response = db
.query("CREATE entry SET name=$name, phone=$phone")
.bind(("name", name))
.bind(("phone", phone))
.await?;
//println!("{:?}", response);
match response.check() {
Ok(_) => return Ok(()),
Err(err) => {
eprintln!("Could not add entry {}", err);
std::process::exit(2);
}
};
}
}
if args.len() == 2 {
let command = &args[1];
if command == "list" {
let mut entries = db
.query("SELECT name, phone FROM type::table($table) ORDER BY name ASC")
.bind(("table", "entry"))
.await?;
let entries: Vec<Entry> = entries.take(0)?;
for entry in entries {
println!("{}: {}", entry.name, entry.phone);
}
return Ok(());
}
}
if args.len() == 3 {
let command = &args[1];
if command == "show" {
let name = &args[2];
let mut entries = db
.query("SELECT name, phone FROM type::table($table) WHERE name=$name")
.bind(("table", "entry"))
.bind(("name", name))
.await?;
let entries: Vec<Entry> = entries.take(0)?;
for entry in entries {
println!("{}: {}", entry.name, entry.phone);
}
return Ok(());
}
if command == "delete" {
let name = &args[2];
let _response = db
.query("DELETE FROM type::table($table) WHERE name=$name")
.bind(("table", "entry"))
.bind(("name", name))
.await?;
return Ok(());
}
}
eprintln!("Usage: {} add name value", args[0]);
std::process::exit(1);
}
examples/surrealdb/cli-phone-book-with-embedded-rocksdb/tests/test_empty.rs
use std::{
os::unix::process::ExitStatusExt,
process::{Command, ExitStatus},
};
use tempdir::TempDir;
#[test]
fn test_phonebook_no_params() {
let tmp_dir = TempDir::new("phonebook").unwrap();
println!("tmp_dir: {:?}", tmp_dir);
std::env::set_var("DATABASE_PATH", tmp_dir.path());
let result = Command::new("cargo")
.args(["run", "-q"])
.output()
.expect("command failed to start");
assert_eq!(std::str::from_utf8(&result.stdout).unwrap(), "");
assert_eq!(
std::str::from_utf8(&result.stderr).unwrap(),
"Usage: target/debug/phonebook add name value\n"
);
assert_eq!(result.status, ExitStatus::from_raw(1 * 256));
}
examples/surrealdb/cli-phone-book-with-embedded-rocksdb/tests/tests.rs
use std::{
os::unix::process::ExitStatusExt,
process::{Command, ExitStatus},
};
use tempdir::TempDir;
#[test]
fn test_phonebook() {
let tmp_dir = TempDir::new("phonebook").unwrap();
println!("tmp_dir: {:?}", tmp_dir);
std::env::set_var("DATABASE_PATH", tmp_dir.path());
let result = Command::new("cargo")
.args(["run", "-q", "add", "foo", "123"])
.output()
.expect("command failed to start");
assert_eq!(std::str::from_utf8(&result.stdout).unwrap(), "");
assert_eq!(std::str::from_utf8(&result.stderr).unwrap(), "");
assert_eq!(result.status, ExitStatus::from_raw(0));
let result = Command::new("cargo")
.args(["run", "-q", "add", "bar", "456"])
.output()
.expect("command failed to start");
assert_eq!(std::str::from_utf8(&result.stdout).unwrap(), "");
assert_eq!(std::str::from_utf8(&result.stderr).unwrap(), "");
assert_eq!(result.status, ExitStatus::from_raw(0));
let result = Command::new("cargo")
.args(["run", "-q", "add", "foo", "789"])
.output()
.expect("command failed to start");
assert_eq!(std::str::from_utf8(&result.stdout).unwrap(), "");
assert!(std::str::from_utf8(&result.stderr).unwrap().contains(
"Could not add entry Database index `entry_email` already contains 'foo', with record"
));
assert_eq!(result.status, ExitStatus::from_raw(2 * 256));
let result = Command::new("cargo")
.args(["run", "-q", "show", "foo"])
.output()
.expect("command failed to start");
assert_eq!(std::str::from_utf8(&result.stdout).unwrap(), "foo: 123\n");
assert_eq!(std::str::from_utf8(&result.stderr).unwrap(), "");
assert_eq!(result.status, ExitStatus::from_raw(0));
let result = Command::new("cargo")
.args(["run", "-q", "show", "bar"])
.output()
.expect("command failed to start");
assert_eq!(std::str::from_utf8(&result.stdout).unwrap(), "bar: 456\n");
assert_eq!(std::str::from_utf8(&result.stderr).unwrap(), "");
assert_eq!(result.status, ExitStatus::from_raw(0));
let result = Command::new("cargo")
.args(["run", "-q", "list"])
.output()
.expect("command failed to start");
//println!("{}", std::str::from_utf8(&result.stdout).unwrap());
assert_eq!(
std::str::from_utf8(&result.stdout).unwrap(),
"bar: 456\nfoo: 123\n"
);
assert_eq!(std::str::from_utf8(&result.stderr).unwrap(), "");
assert_eq!(result.status, ExitStatus::from_raw(0));
let result = Command::new("cargo")
.args(["run", "-q", "delete", "foo"])
.output()
.expect("command failed to start");
assert_eq!(std::str::from_utf8(&result.stdout).unwrap(), "");
assert_eq!(std::str::from_utf8(&result.stderr).unwrap(), "");
assert_eq!(result.status, ExitStatus::from_raw(0));
let result = Command::new("cargo")
.args(["run", "-q", "list"])
.output()
.expect("command failed to start");
//println!("{}", std::str::from_utf8(&result.stdout).unwrap());
assert_eq!(std::str::from_utf8(&result.stdout).unwrap(), "bar: 456\n");
assert_eq!(std::str::from_utf8(&result.stderr).unwrap(), "");
assert_eq!(result.status, ExitStatus::from_raw(0));
drop(tmp_dir);
}