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 pub content: String,
70 pub occurred_at: TimestampMs,
72 pub group_id: String,
74 pub created_at: TimestampMs,
76}
77
78#[derive(Debug, Clone, Default, Serialize, Deserialize)]
81pub struct Subgraph {
82 pub nodes: Vec<Node>,
84 pub edges: Vec<Edge>,
86}
87
88pub(crate) fn uuid_col(row: &Row<'_>, idx: usize) -> rusqlite::Result<Uuid> {
89 let s: String = row.get(idx)?;
90 Uuid::parse_str(&s).map_err(|e| {
91 rusqlite::Error::FromSqlConversionFailure(idx, rusqlite::types::Type::Text, Box::new(e))
92 })
93}
94
95fn uuid_col_opt(row: &Row<'_>, idx: usize) -> rusqlite::Result<Option<Uuid>> {
96 let s: Option<String> = row.get(idx)?;
97 s.map(|s| {
98 Uuid::parse_str(&s).map_err(|e| {
99 rusqlite::Error::FromSqlConversionFailure(idx, rusqlite::types::Type::Text, Box::new(e))
100 })
101 })
102 .transpose()
103}
104
105fn json_col(row: &Row<'_>, idx: usize) -> rusqlite::Result<Json> {
106 let s: String = row.get(idx)?;
107 serde_json::from_str(&s).map_err(|e| {
108 rusqlite::Error::FromSqlConversionFailure(idx, rusqlite::types::Type::Text, Box::new(e))
109 })
110}
111
112pub(crate) const NODE_COLS: &str =
114 "id, kind, name, summary, attrs, group_id, created_at, expired_at, merged_into";
115
116impl Node {
117 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
118 Ok(Self {
119 id: uuid_col(row, 0)?,
120 kind: row.get(1)?,
121 name: row.get(2)?,
122 summary: row.get(3)?,
123 attrs: json_col(row, 4)?,
124 group_id: row.get(5)?,
125 created_at: row.get(6)?,
126 expired_at: row.get(7)?,
127 merged_into: uuid_col_opt(row, 8)?,
128 })
129 }
130}
131
132pub(crate) const EDGE_COLS: &str =
134 "id, src, dst, rel, fact, attrs, group_id, valid_at, invalid_at, created_at, expired_at";
135
136impl Edge {
137 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
138 Ok(Self {
139 id: uuid_col(row, 0)?,
140 src: uuid_col(row, 1)?,
141 dst: uuid_col(row, 2)?,
142 rel: row.get(3)?,
143 fact: row.get(4)?,
144 attrs: json_col(row, 5)?,
145 group_id: row.get(6)?,
146 valid_at: row.get(7)?,
147 invalid_at: row.get(8)?,
148 created_at: row.get(9)?,
149 expired_at: row.get(10)?,
150 })
151 }
152}
153
154pub(crate) const EPISODE_COLS: &str = "id, source, content, occurred_at, group_id, created_at";
156
157impl Episode {
158 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
159 Ok(Self {
160 id: uuid_col(row, 0)?,
161 source: row.get(1)?,
162 content: row.get(2)?,
163 occurred_at: row.get(3)?,
164 group_id: row.get(4)?,
165 created_at: row.get(5)?,
166 })
167 }
168}
169
170pub(crate) fn collect<T>(
171 rows: rusqlite::MappedRows<'_, impl FnMut(&Row<'_>) -> rusqlite::Result<T>>,
172) -> Result<Vec<T>> {
173 let mut out = Vec::new();
174 for row in rows {
175 out.push(row?);
176 }
177 Ok(out)
178}