use rusqlite::Row;
use serde::{Deserialize, Serialize};
use serde_json::Value as Json;
use uuid::Uuid;
use crate::clock::TimestampMs;
use crate::error::Result;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Node {
pub id: Uuid,
pub kind: String,
pub name: String,
pub summary: String,
pub attrs: Json,
pub group_id: String,
pub created_at: TimestampMs,
pub expired_at: Option<TimestampMs>,
pub merged_into: Option<Uuid>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Edge {
pub id: Uuid,
pub src: Uuid,
pub dst: Uuid,
pub rel: String,
pub fact: String,
pub attrs: Json,
pub group_id: String,
pub valid_at: Option<TimestampMs>,
pub invalid_at: Option<TimestampMs>,
pub created_at: TimestampMs,
pub expired_at: Option<TimestampMs>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Episode {
pub id: Uuid,
pub source: String,
#[serde(default)]
pub kind: String,
pub content: String,
pub occurred_at: TimestampMs,
pub group_id: String,
pub created_at: TimestampMs,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Subgraph {
pub nodes: Vec<Node>,
pub edges: Vec<Edge>,
}
pub(crate) fn uuid_col(row: &Row<'_>, idx: usize) -> rusqlite::Result<Uuid> {
let s: String = row.get(idx)?;
Uuid::parse_str(&s).map_err(|e| {
rusqlite::Error::FromSqlConversionFailure(idx, rusqlite::types::Type::Text, Box::new(e))
})
}
fn uuid_col_opt(row: &Row<'_>, idx: usize) -> rusqlite::Result<Option<Uuid>> {
let s: Option<String> = row.get(idx)?;
s.map(|s| {
Uuid::parse_str(&s).map_err(|e| {
rusqlite::Error::FromSqlConversionFailure(idx, rusqlite::types::Type::Text, Box::new(e))
})
})
.transpose()
}
fn json_col(row: &Row<'_>, idx: usize) -> rusqlite::Result<Json> {
let s: String = row.get(idx)?;
serde_json::from_str(&s).map_err(|e| {
rusqlite::Error::FromSqlConversionFailure(idx, rusqlite::types::Type::Text, Box::new(e))
})
}
pub(crate) const NODE_COLS: &str =
"id, kind, name, summary, attrs, group_id, created_at, expired_at, merged_into";
impl Node {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
Ok(Self {
id: uuid_col(row, 0)?,
kind: row.get(1)?,
name: row.get(2)?,
summary: row.get(3)?,
attrs: json_col(row, 4)?,
group_id: row.get(5)?,
created_at: row.get(6)?,
expired_at: row.get(7)?,
merged_into: uuid_col_opt(row, 8)?,
})
}
}
pub(crate) const EDGE_COLS: &str =
"id, src, dst, rel, fact, attrs, group_id, valid_at, invalid_at, created_at, expired_at";
impl Edge {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
Ok(Self {
id: uuid_col(row, 0)?,
src: uuid_col(row, 1)?,
dst: uuid_col(row, 2)?,
rel: row.get(3)?,
fact: row.get(4)?,
attrs: json_col(row, 5)?,
group_id: row.get(6)?,
valid_at: row.get(7)?,
invalid_at: row.get(8)?,
created_at: row.get(9)?,
expired_at: row.get(10)?,
})
}
}
pub(crate) const EPISODE_COLS: &str =
"id, source, kind, content, occurred_at, group_id, created_at";
impl Episode {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
Ok(Self {
id: uuid_col(row, 0)?,
source: row.get(1)?,
kind: row.get(2)?,
content: row.get(3)?,
occurred_at: row.get(4)?,
group_id: row.get(5)?,
created_at: row.get(6)?,
})
}
}
pub(crate) fn collect<T>(
rows: rusqlite::MappedRows<'_, impl FnMut(&Row<'_>) -> rusqlite::Result<T>>,
) -> Result<Vec<T>> {
let mut out = Vec::new();
for row in rows {
out.push(row?);
}
Ok(out)
}