1use std::io::BufRead;
11
12use rushdb_lmpg::Lmpg;
13
14pub fn export_tenant_jsonl(
22 snap: &rushdb_storage::ReadView,
23 tenant: rushdb_types::ids::TenantId,
24) -> Result<(String, u64, u64), String> {
25 let mut bytes = Vec::new();
26 let (records, relationships) = crate::export::export_tenant_jsonl_to(snap, tenant, &mut bytes)?;
27 let jsonl = String::from_utf8(bytes).map_err(|e| e.to_string())?;
28 Ok((jsonl, records, relationships))
29}
30
31fn restore_record(lmpg: &Lmpg, v: &serde_json::Value) -> Result<(), String> {
34 let label = v
35 .get("label")
36 .and_then(|x| x.as_str())
37 .ok_or("backup record missing label")?
38 .to_string();
39 let id = v
40 .get("id")
41 .and_then(|x| x.as_str())
42 .ok_or("backup record missing id")?;
43 let properties = v
44 .get("properties")
45 .cloned()
46 .unwrap_or(serde_json::json!({}));
47 let mut doc = properties.as_object().cloned().unwrap_or_default();
48 doc.insert("__id".to_string(), serde_json::json!(id));
49 doc.insert("__label".to_string(), serde_json::json!(label));
50 rushdb_lmpg::import::upsert_document(lmpg, &label, &serde_json::Value::Object(doc))
51 .map_err(|e| e.to_reason())?;
52 Ok(())
53}
54
55fn restore_relationship(lmpg: &Lmpg, v: &serde_json::Value) -> Result<(), String> {
59 let relationship_id = v
60 .get("id")
61 .and_then(|x| x.as_str())
62 .and_then(rushdb_lmpg::import::decode_simple_hex)
63 .ok_or("backup relationship missing stable id")?;
64 let rel_type = v
65 .get("type")
66 .and_then(|x| x.as_str())
67 .ok_or("backup relationship missing type")?
68 .to_string();
69 let src = v
70 .get("source")
71 .and_then(|x| x.as_str())
72 .and_then(rushdb_lmpg::import::decode_simple_hex)
73 .ok_or("backup relationship bad source id")?;
74 let tgt = v
75 .get("target")
76 .and_then(|x| x.as_str())
77 .and_then(rushdb_lmpg::import::decode_simple_hex)
78 .ok_or("backup relationship bad target id")?;
79 let fields: Vec<(String, rushdb_lmpg::plan::FieldChange)> = v
80 .get("properties")
81 .and_then(serde_json::Value::as_object)
82 .map(|o| {
83 o.iter()
84 .map(|(k, val)| {
85 let change = if val.is_null() {
86 rushdb_lmpg::plan::FieldChange::ExplicitNull
87 } else {
88 rushdb_lmpg::plan::FieldChange::Set(val.clone())
89 };
90 (k.clone(), change)
91 })
92 .collect()
93 })
94 .unwrap_or_default();
95
96 let snap = lmpg.store.read_view().map_err(|e| e.to_string())?;
97 let s_hdr = rushdb_lmpg::reader::try_access::record_by_id(
98 &snap,
99 lmpg.tenant,
100 &rushdb_types::ids::RecordId(src),
101 )
102 .map_err(|e| e.to_string())?
103 .ok_or_else(|| "restore: source record not found".to_string())?;
104 let t_hdr = rushdb_lmpg::reader::try_access::record_by_id(
105 &snap,
106 lmpg.tenant,
107 &rushdb_types::ids::RecordId(tgt),
108 )
109 .map_err(|e| e.to_string())?
110 .ok_or_else(|| "restore: target record not found".to_string())?;
111 if rushdb_lmpg::reader::try_access::relationship_by_id(
114 &snap,
115 lmpg.tenant,
116 &rushdb_types::ids::RelationshipId(relationship_id),
117 )
118 .map_err(|e| e.to_string())?
119 .is_none()
120 {
121 lmpg.create_relationship_with_id(
122 &rel_type,
123 rushdb_lmpg::RecordRef {
124 record_id: src,
125 ordinal: s_hdr.ordinal,
126 },
127 rushdb_lmpg::RecordRef {
128 record_id: tgt,
129 ordinal: t_hdr.ordinal,
130 },
131 relationship_id,
132 fields,
133 )
134 .map_err(|e| e.to_reason())?;
135 }
136 Ok(())
137}
138
139pub fn restore_backup(lmpg: &Lmpg, jsonl: &str) -> Result<(u64, u64), String> {
150 restore_backup_reader(lmpg, std::io::Cursor::new(jsonl.as_bytes()))
151}
152
153pub fn restore_backup_reader<R: BufRead>(lmpg: &Lmpg, reader: R) -> Result<(u64, u64), String> {
161 let mut records = 0u64;
162 let mut relationships = 0u64;
163 for line in reader.lines() {
164 let line = line.map_err(|e| format!("read backup line: {e}"))?;
165 if line.trim().is_empty() {
166 continue;
167 }
168 let v: serde_json::Value = serde_json::from_str(&line).map_err(|e| e.to_string())?;
169 match v.get("kind").and_then(|x| x.as_str()).unwrap_or("") {
170 "record" => {
171 restore_record(lmpg, &v)?;
172 records += 1;
173 }
174 "relationship" => {
175 restore_relationship(lmpg, &v)?;
176 relationships += 1;
177 }
178 other => return Err(format!("unknown backup op kind `{other}`")),
179 }
180 }
181 Ok((records, relationships))
182}