sqlite_knowledge_graph/version/
snapshot.rs1use rusqlite::params;
4
5use super::store;
6use crate::error::Result;
7
8pub fn version_add_entity(
10 conn: &rusqlite::Connection,
11 version_id: i64,
12 entity_id: i64,
13) -> Result<()> {
14 let bit = store::version_bit_for(conn, version_id)?;
15 store::ensure_entity_exists(conn, entity_id)?;
16
17 conn.execute(
18 "UPDATE kg_entities SET validity = COALESCE(validity, 0) | ?1 WHERE id = ?2",
19 params![bit, entity_id],
20 )?;
21 Ok(())
22}
23
24pub fn version_remove_entity(
26 conn: &rusqlite::Connection,
27 version_id: i64,
28 entity_id: i64,
29) -> Result<()> {
30 let bit = store::version_bit_for(conn, version_id)?;
31 store::ensure_entity_exists(conn, entity_id)?;
32
33 conn.execute(
35 "UPDATE kg_entities SET validity = CASE \
36 WHEN (COALESCE(validity, 0) & ~?1) = 0 THEN NULL \
37 ELSE validity & ~?1 \
38 END \
39 WHERE id = ?2",
40 params![bit, entity_id],
41 )?;
42 Ok(())
43}
44
45pub fn version_add_relation(
47 conn: &rusqlite::Connection,
48 version_id: i64,
49 relation_id: i64,
50) -> Result<()> {
51 let bit = store::version_bit_for(conn, version_id)?;
52 store::ensure_relation_exists(conn, relation_id)?;
53
54 conn.execute(
55 "UPDATE kg_relations SET validity = COALESCE(validity, 0) | ?1 WHERE id = ?2",
56 params![bit, relation_id],
57 )?;
58 Ok(())
59}
60
61pub fn version_remove_relation(
63 conn: &rusqlite::Connection,
64 version_id: i64,
65 relation_id: i64,
66) -> Result<()> {
67 let bit = store::version_bit_for(conn, version_id)?;
68 store::ensure_relation_exists(conn, relation_id)?;
69
70 conn.execute(
71 "UPDATE kg_relations SET validity = CASE \
72 WHEN (COALESCE(validity, 0) & ~?1) = 0 THEN NULL \
73 ELSE validity & ~?1 \
74 END \
75 WHERE id = ?2",
76 params![bit, relation_id],
77 )?;
78 Ok(())
79}
80
81pub fn version_snapshot_entities(conn: &rusqlite::Connection, version_id: i64) -> Result<()> {
83 let bit = store::version_bit_for(conn, version_id)?;
84 conn.execute(
85 "UPDATE kg_entities SET validity = COALESCE(validity, 0) | ?1",
86 [bit],
87 )?;
88 Ok(())
89}
90
91pub fn version_snapshot_relations(conn: &rusqlite::Connection, version_id: i64) -> Result<()> {
93 let bit = store::version_bit_for(conn, version_id)?;
94 conn.execute(
95 "UPDATE kg_relations SET validity = COALESCE(validity, 0) | ?1",
96 [bit],
97 )?;
98 Ok(())
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use rusqlite::Connection;
105
106 fn setup() -> Connection {
107 let conn = Connection::open_in_memory().unwrap();
108 crate::schema::create_schema(&conn).unwrap();
109 conn
110 }
111
112 fn insert_entity(conn: &Connection, name: &str) -> i64 {
113 conn.execute(
114 "INSERT INTO kg_entities (entity_type, name) VALUES ('test', ?1)",
115 [name],
116 )
117 .unwrap();
118 conn.last_insert_rowid()
119 }
120
121 fn insert_relation(conn: &Connection, src: i64, tgt: i64) -> i64 {
122 conn.execute(
123 "INSERT INTO kg_relations (source_id, target_id, rel_type) VALUES (?1, ?2, 'rel')",
124 rusqlite::params![src, tgt],
125 )
126 .unwrap();
127 conn.last_insert_rowid()
128 }
129
130 fn get_validity(conn: &Connection, table: &str, id: i64) -> Option<i64> {
131 conn.query_row(
132 &format!("SELECT validity FROM {table} WHERE id = ?1"),
133 [id],
134 |r| r.get(0),
135 )
136 .unwrap()
137 }
138
139 #[test]
140 fn test_add_unversioned_entity_to_version() {
141 let conn = setup();
142 let eid = insert_entity(&conn, "A");
143 let vid = super::super::store::create_version(&conn, "v1", "main", None, None).unwrap();
144
145 version_add_entity(&conn, vid, eid).unwrap();
146
147 let v = get_validity(&conn, "kg_entities", eid);
148 assert_eq!(v, Some(1)); }
150
151 #[test]
152 fn test_add_entity_to_additional_version() {
153 let conn = setup();
154 let eid = insert_entity(&conn, "A");
155 let v1 = super::super::store::create_version(&conn, "v1", "main", None, None).unwrap();
156 let v2 = super::super::store::create_version(&conn, "v2", "main", None, None).unwrap();
157
158 version_add_entity(&conn, v1, eid).unwrap();
159 version_add_entity(&conn, v2, eid).unwrap();
160
161 let v = get_validity(&conn, "kg_entities", eid);
162 assert_eq!(v, Some(0b11)); }
164
165 #[test]
166 fn test_remove_entity_from_one_of_multiple_versions() {
167 let conn = setup();
168 let eid = insert_entity(&conn, "A");
169 let v1 = super::super::store::create_version(&conn, "v1", "main", None, None).unwrap();
170 let v2 = super::super::store::create_version(&conn, "v2", "main", None, None).unwrap();
171
172 version_add_entity(&conn, v1, eid).unwrap();
173 version_add_entity(&conn, v2, eid).unwrap();
174 version_remove_entity(&conn, v1, eid).unwrap();
175
176 let v = get_validity(&conn, "kg_entities", eid);
177 assert_eq!(v, Some(0b10)); }
179
180 #[test]
181 fn test_remove_entity_from_only_version_returns_null() {
182 let conn = setup();
183 let eid = insert_entity(&conn, "A");
184 let v1 = super::super::store::create_version(&conn, "v1", "main", None, None).unwrap();
185
186 version_add_entity(&conn, v1, eid).unwrap();
187 version_remove_entity(&conn, v1, eid).unwrap();
188
189 let v = get_validity(&conn, "kg_entities", eid);
190 assert_eq!(v, None); }
192
193 #[test]
194 fn test_bulk_snapshot_entities() {
195 let conn = setup();
196 let e1 = insert_entity(&conn, "A");
197 let e2 = insert_entity(&conn, "B");
198 let vid = super::super::store::create_version(&conn, "v1", "main", None, None).unwrap();
199
200 version_snapshot_entities(&conn, vid).unwrap();
201
202 assert_eq!(get_validity(&conn, "kg_entities", e1), Some(1));
203 assert_eq!(get_validity(&conn, "kg_entities", e2), Some(1));
204 }
205
206 #[test]
207 fn test_add_relation_to_version() {
208 let conn = setup();
209 let e1 = insert_entity(&conn, "A");
210 let e2 = insert_entity(&conn, "B");
211 let rid = insert_relation(&conn, e1, e2);
212 let vid = super::super::store::create_version(&conn, "v1", "main", None, None).unwrap();
213
214 version_add_relation(&conn, vid, rid).unwrap();
215
216 let v = get_validity(&conn, "kg_relations", rid);
217 assert_eq!(v, Some(1));
218 }
219
220 #[test]
221 fn test_nonexistent_entity_error() {
222 let conn = setup();
223 let vid = super::super::store::create_version(&conn, "v1", "main", None, None).unwrap();
224 let err = version_add_entity(&conn, vid, 999).unwrap_err();
225 assert!(matches!(err, crate::error::Error::EntityNotFound(999)));
226 }
227
228 #[test]
229 fn test_nonexistent_version_error() {
230 let conn = setup();
231 let eid = insert_entity(&conn, "A");
232 let err = version_add_entity(&conn, 999, eid).unwrap_err();
233 assert!(matches!(err, crate::error::Error::VersionNotFound(999)));
234 }
235}