Initial biting implementation
- Rough (untyped) multi-edges implementation - Impl of bites in terms of multi-edges
This commit is contained in:
parent
ad62fec21f
commit
523e9a7479
6 changed files with 121 additions and 7 deletions
|
@ -7,7 +7,7 @@ use puppy::{
|
|||
Error,
|
||||
},
|
||||
tl::Post,
|
||||
Key, Store,
|
||||
Bite, Key, Store,
|
||||
};
|
||||
|
||||
fn main() -> store::Result<()> {
|
||||
|
@ -21,7 +21,7 @@ fn main() -> store::Result<()> {
|
|||
puppy::create_post(&db, riley, "@linen <3")?;
|
||||
puppy::create_post(&db, linen, "@riley <3")?;
|
||||
}
|
||||
if true {
|
||||
if false {
|
||||
println!("making riley follow linen");
|
||||
if !db.exists::<Follows>((riley, linen))? {
|
||||
println!("follow relation does not exist yet");
|
||||
|
@ -57,6 +57,12 @@ fn main() -> store::Result<()> {
|
|||
let (_, Profile { account_name, .. }) = db.lookup(id)?;
|
||||
println!("- @{account_name} ({id})");
|
||||
}
|
||||
println!("Biting riley");
|
||||
puppy::bite_actor(&db, linen, riley).unwrap();
|
||||
for Bite { id, biter, .. } in puppy::bites_on(&db, riley).unwrap() {
|
||||
let (_, Profile { account_name, .. }) = db.lookup(biter).unwrap();
|
||||
println!("riley was bitten by @{account_name} at {}", id.timestamp());
|
||||
}
|
||||
store::OK
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
#![feature(iterator_try_collect)]
|
||||
pub use store::{self, Key, Store};
|
||||
use store::{
|
||||
alias::Username,
|
||||
arrow::AuthorOf,
|
||||
arrow::{self, multi::MultiArrow, AuthorOf},
|
||||
mixin::{Content, Profile},
|
||||
util::IterExt,
|
||||
Keylike,
|
||||
Keylike, Tag,
|
||||
};
|
||||
|
||||
mod tags {
|
||||
|
@ -14,6 +15,7 @@ mod tags {
|
|||
|
||||
pub const ACTOR: Tag = Tag(0);
|
||||
pub const POST: Tag = Tag(1);
|
||||
pub const BITE: Tag = Tag(2);
|
||||
}
|
||||
|
||||
pub fn create_post(db: &Store, author: Key, content: impl ToString) -> store::Result<Key> {
|
||||
|
@ -60,6 +62,34 @@ pub fn list_posts_by_author(
|
|||
})
|
||||
}
|
||||
|
||||
pub struct Bite {
|
||||
pub id: Key,
|
||||
pub biter: Key,
|
||||
pub victim: Key,
|
||||
}
|
||||
|
||||
impl MultiArrow for Bite {
|
||||
const TYPE: Tag = tags::BITE;
|
||||
}
|
||||
|
||||
pub fn bite_actor(db: &Store, biter: Key, victim: Key) -> store::Result<Key> {
|
||||
db.transaction(|tx| {
|
||||
// Bites are represented as multiedges.
|
||||
let key = arrow::multi::insert::<Bite>(&tx, biter, victim)?;
|
||||
// We can treat particular arrows in a quiver as a vertex by registering it.
|
||||
tx.create_vertex(key, tags::BITE)?;
|
||||
Ok(key)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bites_on(db: &Store, victim: Key) -> store::Result<Vec<Bite>> {
|
||||
db.transaction(|tx| {
|
||||
arrow::multi::list_incoming::<Bite>(&tx, victim)
|
||||
.map_ok(|(biter, id)| Bite { id, biter, victim })
|
||||
.try_collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub mod tl {
|
||||
//! Timelines
|
||||
|
||||
|
|
|
@ -4,6 +4,45 @@ use bincode::{Decode, Encode};
|
|||
|
||||
use crate::Space;
|
||||
|
||||
pub mod multi {
|
||||
//! Managing multiedges.
|
||||
//!
|
||||
//! Unlike regular [`Arrow`]s, which don't have an identity (they are identified by the two nodes that
|
||||
//! they connect), multiarrows can have their own [`Key`]. This allows one to have multiple arrows in
|
||||
//! the same direction connecting the same two vertices, which isn't possible with normal arrows.
|
||||
//!
|
||||
//! Multiarrows can also be treated as if they were vertices, if their identity (`Key`) is registered as
|
||||
//! one.
|
||||
//!
|
||||
//! This comes with a trade-off, though, specifically in both space and complexity. A multi-arrow also
|
||||
//! can't have a label, like a typical arrow.
|
||||
|
||||
use crate::{Key, Result, Tag, Transaction};
|
||||
|
||||
pub fn insert<A>(tx: &Transaction<'_>, origin: Key, target: Key) -> Result<Key>
|
||||
where
|
||||
A: MultiArrow,
|
||||
{
|
||||
let key = Key::gen();
|
||||
tx.quiver(A::TYPE).insert(origin, target, key)?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
pub fn list_incoming<'db, A>(
|
||||
tx: &'db Transaction<'db>,
|
||||
target: Key,
|
||||
) -> impl Iterator<Item = Result<(Key, Key)>> + 'db
|
||||
where
|
||||
A: MultiArrow,
|
||||
{
|
||||
tx.quiver(A::TYPE).list_incoming(target)
|
||||
}
|
||||
|
||||
pub trait MultiArrow {
|
||||
const TYPE: Tag;
|
||||
}
|
||||
}
|
||||
|
||||
/// A directed edge between two vertices.
|
||||
pub trait Arrow: Encode + Decode {
|
||||
const SPACE: (Space, Space);
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
use std::fmt::{Debug, Display};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{Alias, Error, Result, Transaction};
|
||||
|
||||
/// A unique identifier for vertices in the database.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Key([u8; 16]);
|
||||
pub struct Key(pub(crate) [u8; 16]);
|
||||
|
||||
impl Display for Key {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&ulid::Ulid::from_bytes(self.0), f)
|
||||
Display::fmt(&Ulid::from_bytes(self.0), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Key {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Key({})", ulid::Ulid::from_bytes(self.0))
|
||||
write!(f, "Key({})", Ulid::from_bytes(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +30,10 @@ impl Key {
|
|||
key.copy_from_slice(&buf);
|
||||
Key(key)
|
||||
}
|
||||
pub fn timestamp(self) -> DateTime<Utc> {
|
||||
let ms = Ulid::from_bytes(self.0).timestamp_ms();
|
||||
DateTime::from_timestamp_millis(ms as i64).unwrap()
|
||||
}
|
||||
/// Join two keys together.
|
||||
pub(crate) fn fuse(self, other: Key) -> [u8; 32] {
|
||||
let mut buf = [0; 32];
|
||||
|
|
|
@ -48,6 +48,9 @@ const SPACES: &[&'static str] = &[
|
|||
"created-by/r",
|
||||
"pending-fr/l",
|
||||
"pending-fr/r",
|
||||
"multi:id-map",
|
||||
"multi:index/l",
|
||||
"multi:index/r",
|
||||
#[cfg(test)]
|
||||
"test-arrow/l",
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -303,6 +303,9 @@ impl Transaction<'_> {
|
|||
decode(v.as_ref()).map(|label| (other, label))
|
||||
})
|
||||
}
|
||||
pub(crate) fn quiver(&self, tag: Tag) -> Quiver<'_> {
|
||||
Quiver { tag, tx: &self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Transaction<'_> {
|
||||
|
@ -386,6 +389,32 @@ impl<'db> Keyspace<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The quiver allows one to manipulate all parallel edges tagged with a particular type.
|
||||
pub struct Quiver<'db> {
|
||||
tx: &'db Transaction<'db>,
|
||||
tag: Tag,
|
||||
}
|
||||
|
||||
impl<'db> Quiver<'db> {
|
||||
pub fn insert(&self, origin: Key, target: Key, identity: Key) -> Result<()> {
|
||||
let fused = origin.fuse(target);
|
||||
self.tx.with("multi:id-map").set(identity, fused)?;
|
||||
let mut triple = [0; 48];
|
||||
triple[..32].copy_from_slice(&fused);
|
||||
triple[32..].copy_from_slice(identity.as_ref());
|
||||
self.tx.with("multi:index/l").set(triple, b"")?;
|
||||
triple[..32].rotate_left(16);
|
||||
self.tx.with("multi:index/r").set(triple, b"")?;
|
||||
OK
|
||||
}
|
||||
pub fn list_incoming(&self, target: Key) -> impl Iterator<Item = Result<(Key, Key)>> + 'db {
|
||||
self.tx
|
||||
.with("multi:index/r")
|
||||
.scan(target)
|
||||
.map_ok(|(k, _)| Key::split(&k.as_ref()[16..]))
|
||||
}
|
||||
}
|
||||
|
||||
fn encode(data: impl Encode) -> Result<Vec<u8>> {
|
||||
bincode::encode_to_vec(data, bincode::config::standard()).map_err(Error::Encoding)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue