Make rocksdb actually work

(also adds some poc social media functions for testing)
This commit is contained in:
Riley Apeldoorn 2024-04-19 01:54:34 +02:00
parent 7c589922e6
commit a8db282cf2
9 changed files with 143 additions and 23 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
/.state

8
Cargo.lock generated
View file

@ -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",

View file

@ -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)
}
} }

View file

@ -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";
''; '';
}; };
}; };

View file

@ -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()
})
}

View file

@ -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"

View file

@ -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);

View file

@ -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 {

View file

@ -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