Make rocksdb actually work
(also adds some poc social media functions for testing)
This commit is contained in:
parent
7c589922e6
commit
a8db282cf2
9 changed files with 143 additions and 23 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
/.state
|
||||||
|
|
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -777,9 +777,8 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librocksdb-sys"
|
name = "librocksdb-sys"
|
||||||
version = "0.16.0+8.10.0"
|
version = "0.17.0+9.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/rust-rocksdb/rust-rocksdb.git#961abc8e45b30b43cad3659305d5703eb349fc31"
|
||||||
checksum = "ce3d60bc059831dc1c83903fb45c103f75db65c5a7bf22272764d9cc683e348c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"bzip2-sys",
|
"bzip2-sys",
|
||||||
|
@ -1196,8 +1195,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rocksdb"
|
name = "rocksdb"
|
||||||
version = "0.22.0"
|
version = "0.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/rust-rocksdb/rust-rocksdb.git#961abc8e45b30b43cad3659305d5703eb349fc31"
|
||||||
checksum = "6bd13e55d6d7b8cd0ea569161127567cd587676c99f4472f779a0279aa60a7a7"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"librocksdb-sys",
|
"librocksdb-sys",
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
use puppy::{store::alias::Username, Store};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("pupctl")
|
let db = Store::open(".state").unwrap();
|
||||||
|
// let riley = puppy::create_author(&db, "riley").unwrap();
|
||||||
|
let riley = db
|
||||||
|
.transaction(|tx| tx.lookup_alias(Username("riley".to_string())))
|
||||||
|
.unwrap();
|
||||||
|
puppy::create_post(&db, riley, "hello!").unwrap();
|
||||||
|
for (key, post) in puppy::list_posts_by_author(&db, riley).unwrap() {
|
||||||
|
println!("post {key}: {:?} by user {riley}", post.content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export LIBCLANG_PATH="${pkgs.llvmPackages_16.libclang.lib}/lib";
|
export LIBCLANG_PATH="${pkgs.llvmPackages_16.libclang.lib}/lib";
|
||||||
export ROCKSDB_LIB_DIR="${pkgs.rocksdb}/lib";
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1 +1,52 @@
|
||||||
|
pub use store::{self, Key, Store};
|
||||||
|
use store::{
|
||||||
|
alias::Username,
|
||||||
|
arrow::{AuthorOf, Direction},
|
||||||
|
value::{Content, Profile},
|
||||||
|
Keylike,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create_post(db: &Store, author: Key, content: impl ToString) -> store::Result<Key> {
|
||||||
|
let key = Key::gen();
|
||||||
|
db.transaction(|tx| {
|
||||||
|
tx.update::<Profile>(author, |_, mut profile| {
|
||||||
|
profile.post_count += 1;
|
||||||
|
Ok(profile)
|
||||||
|
})?;
|
||||||
|
tx.insert(key, Content {
|
||||||
|
content: Some(content.to_string()),
|
||||||
|
summary: None,
|
||||||
|
})?;
|
||||||
|
tx.insert_arrow((author, key), AuthorOf)?;
|
||||||
|
Ok(key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_author(db: &Store, username: impl ToString) -> store::Result<Key> {
|
||||||
|
let key = Key::gen();
|
||||||
|
db.transaction(|tx| {
|
||||||
|
tx.insert_alias(key, Username(username.to_string()))?;
|
||||||
|
tx.insert(key, Profile {
|
||||||
|
post_count: 0,
|
||||||
|
account_name: username.to_string(),
|
||||||
|
display_name: None,
|
||||||
|
about_string: None,
|
||||||
|
about_fields: Vec::new(),
|
||||||
|
})?;
|
||||||
|
Ok(key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_posts_by_author(
|
||||||
|
db: &Store,
|
||||||
|
author: impl Keylike,
|
||||||
|
) -> store::Result<Vec<(Key, Content)>> {
|
||||||
|
db.transaction(|tx| {
|
||||||
|
tx.list_arrows_with::<AuthorOf>(Direction::Outgoing, author)
|
||||||
|
.map(|r| {
|
||||||
|
let (post_key, _) = r?;
|
||||||
|
tx.lookup::<Content>(post_key)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,6 @@ path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ulid = "*"
|
ulid = "*"
|
||||||
rocksdb = "*"
|
rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb.git" }
|
||||||
derive_more = "*"
|
derive_more = "*"
|
||||||
bincode = "2.0.0-rc.3"
|
bincode = "2.0.0-rc.3"
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::{Alias, Result, Transaction};
|
use crate::{Alias, Result, Transaction};
|
||||||
|
|
||||||
/// A unique identifier for vertices in the database.
|
/// A unique identifier for vertices in the database.
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub struct Key([u8; 16]);
|
pub struct Key([u8; 16]);
|
||||||
|
|
||||||
|
impl Display for Key {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
ulid::Ulid::from_bytes(self.0).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Key {
|
impl Key {
|
||||||
|
pub fn gen() -> Key {
|
||||||
|
Key(ulid::Ulid::new().to_bytes())
|
||||||
|
}
|
||||||
pub(crate) fn from_slice(buf: &[u8]) -> Key {
|
pub(crate) fn from_slice(buf: &[u8]) -> Key {
|
||||||
let mut key = [0; 16];
|
let mut key = [0; 16];
|
||||||
key.copy_from_slice(&buf);
|
key.copy_from_slice(&buf);
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use derive_more::From;
|
use derive_more::From;
|
||||||
use rocksdb::MultiThreaded;
|
use rocksdb::{MultiThreaded, Options, TransactionDBOptions};
|
||||||
|
|
||||||
type Backend = rocksdb::TransactionDB<MultiThreaded>;
|
type Backend = rocksdb::TransactionDB<MultiThreaded>;
|
||||||
|
|
||||||
|
@ -23,6 +23,21 @@ pub use key::{Key, Keylike};
|
||||||
pub use transaction::Transaction;
|
pub use transaction::Transaction;
|
||||||
pub use {alias::Alias, arrow::Arrow, value::Value};
|
pub use {alias::Alias, arrow::Arrow, value::Value};
|
||||||
|
|
||||||
|
pub const OK: Result<()> = Ok(());
|
||||||
|
|
||||||
|
/// Master list of all column family names in use.
|
||||||
|
const SPACES: &[&'static str] = &[
|
||||||
|
"registry",
|
||||||
|
"username/l",
|
||||||
|
"username/r",
|
||||||
|
"follows/l",
|
||||||
|
"follows/r",
|
||||||
|
"profile",
|
||||||
|
"content",
|
||||||
|
"created-by/l",
|
||||||
|
"created-by/r",
|
||||||
|
];
|
||||||
|
|
||||||
/// The handle to the data store.
|
/// The handle to the data store.
|
||||||
///
|
///
|
||||||
/// This type can be cloned freely.
|
/// This type can be cloned freely.
|
||||||
|
@ -31,6 +46,23 @@ pub struct Store {
|
||||||
inner: Arc<Backend>,
|
inner: Arc<Backend>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
pub fn open(state_dir: &str) -> Result<Store> {
|
||||||
|
let mut db_opts = Options::default();
|
||||||
|
db_opts.create_if_missing(true);
|
||||||
|
db_opts.create_missing_column_families(true);
|
||||||
|
let tx_opts = TransactionDBOptions::default();
|
||||||
|
// NOTE: it crashes here because there hasn't been a release yet that includes https://github.com/rust-rocksdb/rust-rocksdb/pull/868
|
||||||
|
let inner = Arc::new(Backend::open_cf(
|
||||||
|
&db_opts,
|
||||||
|
&tx_opts,
|
||||||
|
format!("{state_dir}/main-store"),
|
||||||
|
SPACES,
|
||||||
|
)?);
|
||||||
|
Ok(Store { inner })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An isolated keyspace.
|
/// An isolated keyspace.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Space(&'static str);
|
pub struct Space(&'static str);
|
||||||
|
@ -54,11 +86,27 @@ pub mod value {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode)]
|
#[derive(Encode, Decode)]
|
||||||
pub struct Profile {}
|
pub struct Profile {
|
||||||
|
pub post_count: usize,
|
||||||
|
pub account_name: String,
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
pub about_string: Option<String>,
|
||||||
|
pub about_fields: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Value for Profile {
|
impl Value for Profile {
|
||||||
const SPACE: Space = Space("profile");
|
const SPACE: Space = Space("profile");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Encode, Decode)]
|
||||||
|
pub struct Content {
|
||||||
|
pub content: Option<String>,
|
||||||
|
pub summary: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value for Content {
|
||||||
|
const SPACE: Space = Space("content");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod arrow {
|
pub mod arrow {
|
||||||
|
@ -78,6 +126,13 @@ pub mod arrow {
|
||||||
Incoming,
|
Incoming,
|
||||||
Outgoing,
|
Outgoing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Encode, Decode)]
|
||||||
|
pub struct AuthorOf;
|
||||||
|
|
||||||
|
impl Arrow for AuthorOf {
|
||||||
|
const SPACE: (Space, Space) = (Space("created-by/l"), Space("created-by/r"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod alias {
|
pub mod alias {
|
||||||
|
|
|
@ -3,18 +3,9 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
use bincode::{Decode, Encode};
|
use bincode::{Decode, Encode};
|
||||||
use rocksdb::BoundColumnFamily;
|
use rocksdb::BoundColumnFamily;
|
||||||
|
|
||||||
use crate::{arrow::Direction, Alias, Arrow, Backend, Error, Key, Keylike, Result, Store, Value};
|
use crate::{
|
||||||
|
arrow::Direction, Alias, Arrow, Backend, Error, Key, Keylike, Result, Store, Value, OK, SPACES,
|
||||||
const OK: Result<()> = Ok(());
|
};
|
||||||
/// Master list of all column family names in use.
|
|
||||||
const SPACES: &[&'static str] = &[
|
|
||||||
"registry",
|
|
||||||
"username/l",
|
|
||||||
"username/r",
|
|
||||||
"follows/l",
|
|
||||||
"follows/r",
|
|
||||||
"profile",
|
|
||||||
];
|
|
||||||
|
|
||||||
impl Store {
|
impl Store {
|
||||||
/// Initiate a transaction.
|
/// Initiate a transaction.
|
||||||
|
@ -120,17 +111,21 @@ impl Transaction<'_> {
|
||||||
Ok(Key::from_slice(raw.as_ref()))
|
Ok(Key::from_slice(raw.as_ref()))
|
||||||
}
|
}
|
||||||
/// Create a new alias of type `A` for the given [`Key`].
|
/// Create a new alias of type `A` for the given [`Key`].
|
||||||
|
///
|
||||||
|
/// If the alias already exists, this function returns `Conflict`.
|
||||||
pub fn insert_alias<A>(&self, key: Key, alias: A) -> Result<()>
|
pub fn insert_alias<A>(&self, key: Key, alias: A) -> Result<()>
|
||||||
where
|
where
|
||||||
A: Alias,
|
A: Alias,
|
||||||
{
|
{
|
||||||
let (l, r) = A::SPACE;
|
let (l, r) = A::SPACE;
|
||||||
let alias = alias.to_string();
|
let alias = alias.to_string();
|
||||||
|
if self.with(l).has(&alias)? {
|
||||||
|
return Err(Error::Conflict);
|
||||||
|
}
|
||||||
self.with(l).set(&alias, key)?;
|
self.with(l).set(&alias, key)?;
|
||||||
self.with(r).set(key, &alias)?;
|
self.with(r).set(key, &alias)?;
|
||||||
OK
|
OK
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete the alias of type `A` that points to `key`.
|
/// Delete the alias of type `A` that points to `key`.
|
||||||
pub fn remove_alias<A>(&self, key: Key) -> Result<()>
|
pub fn remove_alias<A>(&self, key: Key) -> Result<()>
|
||||||
where
|
where
|
||||||
|
|
Loading…
Reference in a new issue