1use rusqlite::Row;
4use serde::{Deserialize, Serialize};
5use serde_json::Value as Json;
6use uuid::Uuid;
7
8use crate::clock::TimestampMs;
9use crate::error::Result;
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct Node {
14 pub id: Uuid,
16 pub kind: String,
18 pub name: String,
20 pub summary: String,
22 pub attrs: Json,
24 pub group_id: String,
26 pub created_at: TimestampMs,
28 pub expired_at: Option<TimestampMs>,
30 pub merged_into: Option<Uuid>,
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub struct Edge {
37 pub id: Uuid,
39 pub src: Uuid,
41 pub dst: Uuid,
43 pub rel: String,
45 pub fact: String,
47 pub attrs: Json,
49 pub group_id: String,
51 pub valid_at: Option<TimestampMs>,
53 pub invalid_at: Option<TimestampMs>,
55 pub created_at: TimestampMs,
57 pub expired_at: Option<TimestampMs>,
59}
60
61#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63pub struct Episode {
64 pub id: Uuid,
66 pub source: String,
68 #[serde(default)]
72 pub kind: String,
73 pub content: String,
75 pub occurred_at: TimestampMs,
77 pub group_id: String,
79 pub created_at: TimestampMs,
81}
82
83#[derive(Debug, Clone, Default, Serialize, Deserialize)]
86pub struct Subgraph {
87 pub nodes: Vec<Node>,
89 pub edges: Vec<Edge>,
91}
92
93pub(crate) fn uuid_col(row: &Row<'_>, idx: usize) -> rusqlite::Result<Uuid> {
94 let s: String = row.get(idx)?;
95 Uuid::parse_str(&s).map_err(|e| {
96 rusqlite::Error::FromSqlConversionFailure(idx, rusqlite::types::Type::Text, Box::new(e))
97 })
98}
99
100fn uuid_col_opt(row: &Row<'_>, idx: usize) -> rusqlite::Result<Option<Uuid>> {
101 let s: Option<String> = row.get(idx)?;
102 s.map(|s| {
103 Uuid::parse_str(&s).map_err(|e| {
104 rusqlite::Error::FromSqlConversionFailure(idx, rusqlite::types::Type::Text, Box::new(e))
105 })
106 })
107 .transpose()
108}
109
110fn json_col(row: &Row<'_>, idx: usize) -> rusqlite::Result<Json> {
111 let s: String = row.get(idx)?;
112 serde_json::from_str(&s).map_err(|e| {
113 rusqlite::Error::FromSqlConversionFailure(idx, rusqlite::types::Type::Text, Box::new(e))
114 })
115}
116
117pub(crate) const NODE_COLS: &str =
119 "id, kind, name, summary, attrs, group_id, created_at, expired_at, merged_into";
120
121impl Node {
122 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
123 Ok(Self {
124 id: uuid_col(row, 0)?,
125 kind: row.get(1)?,
126 name: row.get(2)?,
127 summary: row.get(3)?,
128 attrs: json_col(row, 4)?,
129 group_id: row.get(5)?,
130 created_at: row.get(6)?,
131 expired_at: row.get(7)?,
132 merged_into: uuid_col_opt(row, 8)?,
133 })
134 }
135}
136
137pub(crate) const EDGE_COLS: &str =
139 "id, src, dst, rel, fact, attrs, group_id, valid_at, invalid_at, created_at, expired_at";
140
141impl Edge {
142 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
143 Ok(Self {
144 id: uuid_col(row, 0)?,
145 src: uuid_col(row, 1)?,
146 dst: uuid_col(row, 2)?,
147 rel: row.get(3)?,
148 fact: row.get(4)?,
149 attrs: json_col(row, 5)?,
150 group_id: row.get(6)?,
151 valid_at: row.get(7)?,
152 invalid_at: row.get(8)?,
153 created_at: row.get(9)?,
154 expired_at: row.get(10)?,
155 })
156 }
157}
158
159pub(crate) const EPISODE_COLS: &str =
161 "id, source, kind, content, occurred_at, group_id, created_at";
162
163impl Episode {
164 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
165 Ok(Self {
166 id: uuid_col(row, 0)?,
167 source: row.get(1)?,
168 kind: row.get(2)?,
169 content: row.get(3)?,
170 occurred_at: row.get(4)?,
171 group_id: row.get(5)?,
172 created_at: row.get(6)?,
173 })
174 }
175}
176
177pub(crate) fn collect<T>(
178 rows: rusqlite::MappedRows<'_, impl FnMut(&Row<'_>) -> rusqlite::Result<T>>,
179) -> Result<Vec<T>> {
180 let mut out = Vec::new();
181 for row in rows {
182 out.push(row?);
183 }
184 Ok(out)
185}