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,
|
Error,
|
||||||
},
|
},
|
||||||
tl::Post,
|
tl::Post,
|
||||||
Key, Store,
|
Bite, Key, Store,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() -> store::Result<()> {
|
fn main() -> store::Result<()> {
|
||||||
|
@ -21,7 +21,7 @@ fn main() -> store::Result<()> {
|
||||||
puppy::create_post(&db, riley, "@linen <3")?;
|
puppy::create_post(&db, riley, "@linen <3")?;
|
||||||
puppy::create_post(&db, linen, "@riley <3")?;
|
puppy::create_post(&db, linen, "@riley <3")?;
|
||||||
}
|
}
|
||||||
if true {
|
if false {
|
||||||
println!("making riley follow linen");
|
println!("making riley follow linen");
|
||||||
if !db.exists::<Follows>((riley, linen))? {
|
if !db.exists::<Follows>((riley, linen))? {
|
||||||
println!("follow relation does not exist yet");
|
println!("follow relation does not exist yet");
|
||||||
|
@ -57,6 +57,12 @@ fn main() -> store::Result<()> {
|
||||||
let (_, Profile { account_name, .. }) = db.lookup(id)?;
|
let (_, Profile { account_name, .. }) = db.lookup(id)?;
|
||||||
println!("- @{account_name} ({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
|
store::OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
#![feature(iterator_try_collect)]
|
||||||
pub use store::{self, Key, Store};
|
pub use store::{self, Key, Store};
|
||||||
use store::{
|
use store::{
|
||||||
alias::Username,
|
alias::Username,
|
||||||
arrow::AuthorOf,
|
arrow::{self, multi::MultiArrow, AuthorOf},
|
||||||
mixin::{Content, Profile},
|
mixin::{Content, Profile},
|
||||||
util::IterExt,
|
util::IterExt,
|
||||||
Keylike,
|
Keylike, Tag,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod tags {
|
mod tags {
|
||||||
|
@ -14,6 +15,7 @@ mod tags {
|
||||||
|
|
||||||
pub const ACTOR: Tag = Tag(0);
|
pub const ACTOR: Tag = Tag(0);
|
||||||
pub const POST: Tag = Tag(1);
|
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> {
|
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 {
|
pub mod tl {
|
||||||
//! Timelines
|
//! Timelines
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,45 @@ use bincode::{Decode, Encode};
|
||||||
|
|
||||||
use crate::Space;
|
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.
|
/// A directed edge between two vertices.
|
||||||
pub trait Arrow: Encode + Decode {
|
pub trait Arrow: Encode + Decode {
|
||||||
const SPACE: (Space, Space);
|
const SPACE: (Space, Space);
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use ulid::Ulid;
|
||||||
|
|
||||||
use crate::{Alias, Error, Result, Transaction};
|
use crate::{Alias, Error, 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, Hash)]
|
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct Key([u8; 16]);
|
pub struct Key(pub(crate) [u8; 16]);
|
||||||
|
|
||||||
impl Display for Key {
|
impl Display for Key {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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 {
|
impl Debug for Key {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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.copy_from_slice(&buf);
|
||||||
Key(key)
|
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.
|
/// Join two keys together.
|
||||||
pub(crate) fn fuse(self, other: Key) -> [u8; 32] {
|
pub(crate) fn fuse(self, other: Key) -> [u8; 32] {
|
||||||
let mut buf = [0; 32];
|
let mut buf = [0; 32];
|
||||||
|
|
|
@ -48,6 +48,9 @@ const SPACES: &[&'static str] = &[
|
||||||
"created-by/r",
|
"created-by/r",
|
||||||
"pending-fr/l",
|
"pending-fr/l",
|
||||||
"pending-fr/r",
|
"pending-fr/r",
|
||||||
|
"multi:id-map",
|
||||||
|
"multi:index/l",
|
||||||
|
"multi:index/r",
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
"test-arrow/l",
|
"test-arrow/l",
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -303,6 +303,9 @@ impl Transaction<'_> {
|
||||||
decode(v.as_ref()).map(|label| (other, label))
|
decode(v.as_ref()).map(|label| (other, label))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub(crate) fn quiver(&self, tag: Tag) -> Quiver<'_> {
|
||||||
|
Quiver { tag, tx: &self }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction<'_> {
|
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>> {
|
fn encode(data: impl Encode) -> Result<Vec<u8>> {
|
||||||
bincode::encode_to_vec(data, bincode::config::standard()).map_err(Error::Encoding)
|
bincode::encode_to_vec(data, bincode::config::standard()).map_err(Error::Encoding)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue