clean up code
This commit is contained in:
parent
262415b29c
commit
7c2575990e
7 changed files with 116 additions and 103 deletions
|
@ -1,6 +1,6 @@
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
@ -8,9 +8,9 @@ use clap::Parser;
|
|||
use dag_resolve::{
|
||||
actor::{ActorId, ActorSecret},
|
||||
event::{CoreContent, Event, HashType, SignatureType},
|
||||
resolver::Resolver,
|
||||
resolvers::kv::{KVEventContent, KVResolver},
|
||||
room::Room,
|
||||
store::sqlite,
|
||||
Error,
|
||||
};
|
||||
use rusqlite::Connection;
|
||||
|
@ -33,8 +33,8 @@ enum Cli {
|
|||
}
|
||||
|
||||
struct State {
|
||||
db: Arc<Connection>,
|
||||
room: Room<KVEventContent, ()>,
|
||||
db: Rc<Connection>,
|
||||
room: Room<KVResolver>,
|
||||
secret: ActorSecret,
|
||||
store: Box<dag_resolve::store::sqlite::Database>,
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ fn get_repo(path: &Path, create: bool) -> Result<State> {
|
|||
"#,
|
||||
)?;
|
||||
let (actor, secret) = ActorId::new(SignatureType::Ed25519);
|
||||
let resolver = Box::new(KVResolver::<KVEventContent, sqlite::Table>::new());
|
||||
let resolver = KVResolver::new();
|
||||
let room = Room::builder()
|
||||
.with_resolver(resolver)
|
||||
.with_hasher(HashType::Sha256)
|
||||
|
@ -110,7 +110,7 @@ fn get_repo(path: &Path, create: bool) -> Result<State> {
|
|||
(event.id().to_string(), serde_json::to_string(&event)?),
|
||||
)?;
|
||||
}
|
||||
let db = Arc::new(db);
|
||||
let db = Rc::new(db);
|
||||
let store = dag_resolve::store::sqlite::Database::from_conn(db.clone())
|
||||
.init(room.get_resolver().get_state_config())?;
|
||||
Ok(State {
|
||||
|
@ -135,7 +135,7 @@ fn get_repo(path: &Path, create: bool) -> Result<State> {
|
|||
dbg!(&actor, &secret);
|
||||
let mut q = db.prepare("SELECT json FROM events")?;
|
||||
let mut rows = q.query([])?;
|
||||
let mut room: Option<Room<KVEventContent, ()>> = None;
|
||||
let mut room: Option<Room<KVResolver>> = None;
|
||||
while let Some(row) = rows.next()? {
|
||||
let s: String = dbg!(row.get(0)?);
|
||||
let ev = dbg!(serde_json::from_str(&s)?);
|
||||
|
@ -144,7 +144,7 @@ fn get_repo(path: &Path, create: bool) -> Result<State> {
|
|||
r.append_event(ev)?;
|
||||
}
|
||||
None => {
|
||||
let resolver = Box::new(KVResolver::<KVEventContent, sqlite::Table>::new());
|
||||
let resolver = KVResolver::new();
|
||||
let r = Room::from_root(resolver, ev)?;
|
||||
room = Some(r);
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ fn get_repo(path: &Path, create: bool) -> Result<State> {
|
|||
drop(rows);
|
||||
drop(q);
|
||||
let room = room.unwrap();
|
||||
let db = Arc::new(db);
|
||||
let db = Rc::new(db);
|
||||
dbg!(&room);
|
||||
let store = dag_resolve::store::sqlite::Database::from_conn(db.clone())
|
||||
.init(room.get_resolver().get_state_config())?;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{
|
||||
actor::{ActorId, ActorSecret, ActorSignature},
|
||||
resolver::Resolver,
|
||||
room::Room,
|
||||
Error, Result,
|
||||
};
|
||||
|
@ -148,7 +149,7 @@ impl<T: Debug + Serialize + Clone> Event<T> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_room<S>(&self, room: &Room<T, S>) -> Result<()> {
|
||||
pub fn verify_room<R: Resolver<EventType = T>>(&self, room: &Room<R>) -> Result<()> {
|
||||
self.verify()?;
|
||||
|
||||
let room_config = match &room.get_root().content {
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::table::{State, StateConfig};
|
||||
use crate::event::{Event, EventId};
|
||||
use std::{cmp::Ordering, collections::HashSet, fmt::Debug};
|
||||
use crate::store::sqlite;
|
||||
use super::table::{State, StateConfig};
|
||||
|
||||
/// small shards of code designed to resolve state
|
||||
pub trait Resolver<Type, S> {
|
||||
pub trait Resolver {
|
||||
type EventType: Debug + Serialize + Clone;
|
||||
|
||||
/// Given a set of ordered events, resolve the final state
|
||||
fn resolve(&self, state: &mut dyn State<Table = sqlite::Table, Err = sqlite::Error>, event: &Event<Type>);
|
||||
fn resolve<S: State>(&self, state: &mut S, event: &Event<Self::EventType>);
|
||||
|
||||
/// Given two events, decide which one comes first
|
||||
/// if Ordering::Equal is returned, the event id is used
|
||||
fn tiebreak(&self, a: &Event<Type>, b: &Event<Type>) -> Ordering;
|
||||
fn tiebreak(&self, a: &Event<Self::EventType>, b: &Event<Self::EventType>) -> Ordering;
|
||||
|
||||
/// TEMP: Get the name/id of this resolver
|
||||
fn name(&self) -> &str;
|
||||
|
@ -53,14 +54,13 @@ pub fn sort<T: Debug + Serialize + Clone>(
|
|||
sorted
|
||||
}
|
||||
|
||||
impl<T, S> Debug for dyn Resolver<T, S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Resolver({} -> {})",
|
||||
std::any::type_name::<T>(),
|
||||
std::any::type_name::<S>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// impl<T, S> Debug for dyn Resolver<T, S> {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// write!(
|
||||
// f,
|
||||
// "Resolver({} -> {})",
|
||||
// std::any::type_name::<T>(),
|
||||
// std::any::type_name::<S>()
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -1,25 +1,28 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
actor::ActorSecret, event::EventId, event::{CoreContent, CreateContent, Event, HashType, SignatureType}, proto, resolver::{sort, Resolver}, store::sqlite, Error, Result
|
||||
actor::ActorSecret,
|
||||
event::EventId,
|
||||
event::{CoreContent, CreateContent, Event, HashType, SignatureType},
|
||||
proto,
|
||||
resolver::{sort, Resolver},
|
||||
Error, Result,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Room<T, S> {
|
||||
pub events: Vec<Event<T>>,
|
||||
pub struct Room<R: Resolver> {
|
||||
pub events: Vec<Event<R::EventType>>,
|
||||
pub heads: Vec<EventId>,
|
||||
pub resolver: Box<dyn Resolver<T, S>>,
|
||||
pub resolver: R,
|
||||
}
|
||||
|
||||
impl<T: Debug + Serialize + Clone, S> Room<T, S> {
|
||||
pub fn builder() -> RoomBuilder<T, S> {
|
||||
impl<R: Resolver> Room<R> {
|
||||
pub fn builder() -> RoomBuilder<R> {
|
||||
RoomBuilder::new()
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
resolver_name: impl Into<String>,
|
||||
resolver: Box<dyn Resolver<T, S>>,
|
||||
resolver: R,
|
||||
hasher: HashType,
|
||||
signer: SignatureType,
|
||||
secret: &ActorSecret,
|
||||
|
@ -41,7 +44,7 @@ impl<T: Debug + Serialize + Clone, S> Room<T, S> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn from_root(resolver: Box<dyn Resolver<T, S>>, event: Event<T>) -> Result<Self> {
|
||||
pub fn from_root(resolver: R, event: Event<R::EventType>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
heads: vec![event.id().clone()],
|
||||
events: vec![event],
|
||||
|
@ -49,19 +52,20 @@ impl<T: Debug + Serialize + Clone, S> Room<T, S> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_root(&self) -> &Event<T> {
|
||||
pub fn get_root(&self) -> &Event<R::EventType> {
|
||||
self.events
|
||||
.first()
|
||||
.as_ref()
|
||||
.expect("room always has a root event")
|
||||
}
|
||||
|
||||
pub fn get_resolver(&self) -> &dyn Resolver<T, S> {
|
||||
self.resolver.as_ref()
|
||||
pub fn get_resolver(&self) -> &R {
|
||||
&self.resolver
|
||||
}
|
||||
|
||||
pub fn resolve_state<A>(&mut self, initial_state: A) -> A
|
||||
where A: proto::table::State<Err = sqlite::Error, Table = sqlite::Table>
|
||||
where
|
||||
A: proto::table::State,
|
||||
{
|
||||
let resolver = self.get_resolver();
|
||||
let sorted = sort(|a, b| resolver.tiebreak(a, b), &self.events);
|
||||
|
@ -74,9 +78,9 @@ impl<T: Debug + Serialize + Clone, S> Room<T, S> {
|
|||
|
||||
pub fn create_event(
|
||||
&mut self,
|
||||
event_content: CoreContent<T>,
|
||||
event_content: CoreContent<R::EventType>,
|
||||
secret: &ActorSecret,
|
||||
) -> Result<&Event<T>> {
|
||||
) -> Result<&Event<R::EventType>> {
|
||||
let event = Event::builder(event_content, secret)
|
||||
.with_references(std::mem::take(&mut self.heads))
|
||||
.then_hash()?
|
||||
|
@ -84,9 +88,9 @@ impl<T: Debug + Serialize + Clone, S> Room<T, S> {
|
|||
self.append_event(event)
|
||||
}
|
||||
|
||||
pub fn append_event<'a>(&'a mut self, event: Event<T>) -> Result<&'a Event<T>> {
|
||||
pub fn append_event(&mut self, event: Event<R::EventType>) -> Result<&Event<R::EventType>> {
|
||||
event.verify_room(self).expect("event failed verification");
|
||||
if let Some(_) = self.events.iter().find(|p| p == &&event) {
|
||||
if self.events.iter().any(|p| p == &event) {
|
||||
return Err(Error::AlreadyExists);
|
||||
}
|
||||
self.events.push(event);
|
||||
|
@ -97,13 +101,13 @@ impl<T: Debug + Serialize + Clone, S> Room<T, S> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct RoomBuilder<T, S> {
|
||||
resolver: Option<Box<dyn Resolver<T, S>>>,
|
||||
pub struct RoomBuilder<R: Resolver> {
|
||||
resolver: Option<R>,
|
||||
hasher: Option<HashType>,
|
||||
signer: Option<SignatureType>,
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Serialize, S> Default for RoomBuilder<T, S> {
|
||||
impl<R: Resolver> Default for RoomBuilder<R> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
resolver: None,
|
||||
|
@ -113,12 +117,12 @@ impl<T: Clone + Debug + Serialize, S> Default for RoomBuilder<T, S> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Serialize, S> RoomBuilder<T, S> {
|
||||
impl<R: Resolver> RoomBuilder<R> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_resolver(mut self, resolver: Box<dyn Resolver<T, S>>) -> Self {
|
||||
pub fn with_resolver(mut self, resolver: R) -> Self {
|
||||
self.resolver = Some(resolver);
|
||||
self
|
||||
}
|
||||
|
@ -133,8 +137,8 @@ impl<T: Clone + Debug + Serialize, S> RoomBuilder<T, S> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn create(self, secret: &ActorSecret) -> Result<Room<T, S>> {
|
||||
Room::<T, S>::new(
|
||||
pub fn create(self, secret: &ActorSecret) -> Result<Room<R>> {
|
||||
Room::new(
|
||||
self.resolver
|
||||
.as_ref()
|
||||
.ok_or(Error::MissingBuilderData)?
|
||||
|
|
|
@ -6,42 +6,42 @@ use crate::{
|
|||
event::{CoreContent, Event},
|
||||
proto::table::{ColumnType, IndexType, State, StateConfig, Table, TableConfig, TableRow},
|
||||
resolver::Resolver,
|
||||
store::sqlite,
|
||||
};
|
||||
use std::{cmp::Ordering, collections::HashMap, fmt::Debug, marker::PhantomData};
|
||||
use std::{cmp::Ordering, collections::HashMap, fmt::Debug};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
/// A basic key-value store
|
||||
pub struct KVResolver<A, B> {
|
||||
_a: PhantomData<A>,
|
||||
_b: PhantomData<B>,
|
||||
}
|
||||
pub struct KVResolver;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum KVEventContent {
|
||||
Set(String, String),
|
||||
}
|
||||
|
||||
impl<A, B> KVResolver<A, B> {
|
||||
impl KVResolver {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_a: PhantomData,
|
||||
_b: PhantomData,
|
||||
}
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Debug, B: Table> Resolver<KVEventContent, ()> for KVResolver<A, B> {
|
||||
fn resolve(&self, state: &mut dyn State<Table = sqlite::Table, Err = sqlite::Error>, event: &Event<KVEventContent>) {
|
||||
impl Resolver for KVResolver {
|
||||
type EventType = KVEventContent;
|
||||
|
||||
fn resolve<S: State>(&self, state: &mut S, event: &Event<KVEventContent>) {
|
||||
dbg!(event);
|
||||
let table = state.table("kv").unwrap();
|
||||
let table = match state.table("kv") {
|
||||
Ok(t) => t,
|
||||
Err(_) => panic!("no table exists"),
|
||||
};
|
||||
match &event.content() {
|
||||
CoreContent::Create(_) => {}
|
||||
CoreContent::Custom(KVEventContent::Set(k, v)) => {
|
||||
let res = table.insert(TableRow { values: HashMap::from_iter([
|
||||
("key".into(), k.to_owned().into()),
|
||||
("value".into(), v.to_owned().into()),
|
||||
]) });
|
||||
let res = table.insert(TableRow {
|
||||
values: HashMap::from_iter([
|
||||
("key".into(), k.to_owned().into()),
|
||||
("value".into(), v.to_owned().into()),
|
||||
]),
|
||||
});
|
||||
if res.is_err() {
|
||||
panic!("could not insert");
|
||||
}
|
||||
|
|
|
@ -1,55 +1,56 @@
|
|||
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||
use std::{collections::HashMap, path::Path, rc::Rc};
|
||||
|
||||
use crate::proto::table;
|
||||
use rusqlite::params_from_iter;
|
||||
use thiserror::Error;
|
||||
use crate::proto::table;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Database {
|
||||
connection: Arc<rusqlite::Connection>,
|
||||
connection: Rc<rusqlite::Connection>,
|
||||
config: Option<table::StateConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Table {
|
||||
connection: Arc<rusqlite::Connection>,
|
||||
connection: Rc<rusqlite::Connection>,
|
||||
name: String,
|
||||
config: table::TableConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
|
||||
}
|
||||
pub enum Error {}
|
||||
|
||||
impl Database {
|
||||
pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> {
|
||||
let db = rusqlite::Connection::open(path).unwrap();
|
||||
Ok(Database {
|
||||
connection: Arc::new(db),
|
||||
connection: Rc::new(db),
|
||||
config: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_conn(conn: Arc<rusqlite::Connection>) -> Self {
|
||||
Database { connection: conn, config: None }
|
||||
pub fn from_conn(conn: Rc<rusqlite::Connection>) -> Self {
|
||||
Database {
|
||||
connection: conn,
|
||||
config: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn init(mut self, config: table::StateConfig) -> Result<Box<Self>, Error> {
|
||||
let mut sql = String::new();
|
||||
for (table_name, table_column) in &config.tables {
|
||||
// FIXME: don't drop and resolve from scratch every time
|
||||
sql.push_str("DROP TABLE IF EXISTS '");
|
||||
sql.push_str(&table_name.replace("'", "''"));
|
||||
sql.push_str(&table_name.replace('\'', "''"));
|
||||
sql.push_str("';\n");
|
||||
sql.push_str("CREATE TABLE '");
|
||||
sql.push_str(&table_name.replace("'", "''"));
|
||||
sql.push_str(&table_name.replace('\'', "''"));
|
||||
sql.push_str("' (");
|
||||
for (idx, (column_name, column_config)) in table_column.columns.iter().enumerate() {
|
||||
if idx != 0 {
|
||||
sql.push_str(", ");
|
||||
}
|
||||
sql.push_str(&column_name.replace("'", "''"));
|
||||
sql.push_str(&column_name.replace('\'', "''"));
|
||||
sql.push_str(match column_config {
|
||||
table::ColumnType::String => " TEXT",
|
||||
table::ColumnType::Text => " TEXT",
|
||||
|
@ -95,25 +96,24 @@ impl table::Table for Table {
|
|||
};
|
||||
let has_index = self.config.indexes.iter().any(|idx| {
|
||||
idx.column == column
|
||||
&& match idx.index_type {
|
||||
table::IndexType::Lookup => true,
|
||||
table::IndexType::LookupUnique => true,
|
||||
_ => false,
|
||||
}
|
||||
&& matches!(
|
||||
idx.index_type,
|
||||
table::IndexType::Lookup | table::IndexType::LookupUnique
|
||||
)
|
||||
});
|
||||
if !has_index {
|
||||
panic!("no lookup index");
|
||||
}
|
||||
let mut sql = String::from("SELECT * FROM ");
|
||||
sql.push_str(&self.name.replace("'", "''"));
|
||||
sql.push_str(&self.name.replace('\'', "''"));
|
||||
sql.push_str(" WHERE ");
|
||||
sql.push_str(&column.replace("'", "''"));
|
||||
sql.push_str(&column.replace('\'', "''"));
|
||||
sql.push_str(" = ");
|
||||
match (column_config, value.into()) {
|
||||
(table::ColumnType::String, table::ColumnValue::String(s)) => {
|
||||
sql.push_str("'");
|
||||
sql.push_str(&s.replace("'", "''"));
|
||||
sql.push_str("'");
|
||||
sql.push('\'');
|
||||
sql.push_str(&s.replace('\'', "''"));
|
||||
sql.push('\'');
|
||||
}
|
||||
(table::ColumnType::Integer, table::ColumnValue::Integer(i)) => {
|
||||
sql.push_str(&i.to_string());
|
||||
|
@ -188,13 +188,13 @@ impl table::Table for Table {
|
|||
|
||||
fn insert(&self, mut row: table::TableRow) -> Result<(), Self::Err> {
|
||||
let mut sql = String::from("INSERT INTO ");
|
||||
sql.push_str(&self.name.replace("'", "''"));
|
||||
sql.push_str(&self.name.replace('\'', "''"));
|
||||
sql.push_str(" (");
|
||||
for (idx, name) in self.config.columns.keys().enumerate() {
|
||||
if idx != 0 {
|
||||
sql.push_str(", ");
|
||||
}
|
||||
sql.push_str(&name.replace("'", "''"));
|
||||
sql.push_str(&name.replace('\'', "''"));
|
||||
}
|
||||
sql.push_str(") VALUES (");
|
||||
use rusqlite::types::Value;
|
||||
|
@ -203,16 +203,24 @@ impl table::Table for Table {
|
|||
if idx != 0 {
|
||||
sql.push_str(", ");
|
||||
}
|
||||
sql.push_str("?");
|
||||
sql.push('?');
|
||||
match (column_type, row.values.remove(name).unwrap()) {
|
||||
(table::ColumnType::String, table::ColumnValue::String(s)) => params.push(Value::Text(s)),
|
||||
(table::ColumnType::Integer, table::ColumnValue::Integer(i)) => params.push(Value::Integer(i.try_into().unwrap())),
|
||||
(table::ColumnType::Float, table::ColumnValue::Float(r)) => params.push(Value::Real(r)),
|
||||
(table::ColumnType::String, table::ColumnValue::String(s)) => {
|
||||
params.push(Value::Text(s))
|
||||
}
|
||||
(table::ColumnType::Integer, table::ColumnValue::Integer(i)) => {
|
||||
params.push(Value::Integer(i.try_into().unwrap()))
|
||||
}
|
||||
(table::ColumnType::Float, table::ColumnValue::Float(r)) => {
|
||||
params.push(Value::Real(r))
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
}
|
||||
sql.push_str(")");
|
||||
self.connection.execute(&sql, params_from_iter(params)).unwrap();
|
||||
sql.push(')');
|
||||
self.connection
|
||||
.execute(&sql, params_from_iter(params))
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use dag_resolve::{
|
|||
#[test]
|
||||
fn test_example() {
|
||||
let (_actor, secret) = ActorId::new(SignatureType::Ed25519);
|
||||
let resolver = Box::new(KVResolver);
|
||||
let resolver = KVResolver;
|
||||
let mut room = Room::builder()
|
||||
.with_resolver(resolver)
|
||||
.with_hasher(HashType::Sha256)
|
||||
|
|
Loading…
Reference in a new issue