nodedb_lite/nodedb/collection/
import.rs1use nodedb_types::error::{NodeDbError, NodeDbResult};
4
5use super::super::{LockExt, NodeDbLite};
6use crate::storage::engine::StorageEngine;
7
8impl<S: StorageEngine> NodeDbLite<S> {
9 pub async fn copy_from_ndjson(&self, collection: &str, ndjson: &str) -> NodeDbResult<u64> {
16 let guard = self.fts_state.admit_mutation().await;
17 let result = async {
18 let mut docs: Vec<(String, Vec<(String, loro::LoroValue)>)> = Vec::new();
19 for line in ndjson.lines() {
20 let line = line.trim();
21 if line.is_empty() || line.starts_with('#') {
22 continue;
23 }
24 let obj: serde_json::Value = sonic_rs::from_str(line)
25 .map_err(|e| NodeDbError::bad_request(format!("invalid JSON: {e}")))?;
26
27 let id = obj
28 .get("id")
29 .and_then(|v| v.as_str())
30 .map(|s| s.to_string())
31 .unwrap_or_else(nodedb_types::id_gen::uuid_v7);
32
33 let mut fields = Vec::new();
34 if let serde_json::Value::Object(map) = &obj {
35 for (k, v) in map {
36 if k == "id" {
37 continue;
38 }
39 fields.push((k.clone(), json_to_loro(v)));
40 }
41 }
42 docs.push((id, fields));
43 }
44
45 let mut crdt = self.crdt.lock_or_recover();
46 let count = docs.len() as u64;
47
48 use crate::engine::crdt::engine::CrdtField;
49 let borrowed_fields: Vec<Vec<CrdtField<'_>>> = docs
50 .iter()
51 .map(|(_, fields)| {
52 fields
53 .iter()
54 .map(|(k, v)| (k.as_str(), v.clone()))
55 .collect()
56 })
57 .collect();
58
59 let ops: Vec<(&str, &str, &[CrdtField<'_>])> = docs
60 .iter()
61 .zip(borrowed_fields.iter())
62 .map(|((id, _), fields)| (collection, id.as_str(), fields.as_slice()))
63 .collect();
64
65 crdt.batch_upsert(&ops).map_err(NodeDbError::from)?;
66 drop(crdt);
67 self.reindex_documents_local(collection, docs.iter().map(|(id, _)| id.as_str()))?;
68 Ok(count)
69 }
70 .await;
71 guard.finish(result)
72 }
73
74 pub async fn copy_from_csv(&self, collection: &str, csv_text: &str) -> NodeDbResult<u64> {
83 let mut lines_iter = csv_text.lines();
84 let header = lines_iter
85 .next()
86 .ok_or_else(|| NodeDbError::bad_request("CSV has no header"))?;
87 let columns: Vec<&str> = header.split(',').map(|s| s.trim()).collect();
88 let id_col = columns.iter().position(|c| *c == "id");
89
90 let rows: Vec<Vec<&str>> = lines_iter
92 .filter(|l| !l.trim().is_empty())
93 .map(|l| l.split(',').map(str::trim).collect())
94 .collect();
95
96 let col_types: Vec<&str> = (0..columns.len())
100 .map(|ci| {
101 let all_int = rows
102 .iter()
103 .all(|r| r.get(ci).map(|v| v.parse::<i64>().is_ok()).unwrap_or(true));
104 if all_int {
105 return "int";
106 }
107 let all_float = rows
108 .iter()
109 .all(|r| r.get(ci).map(|v| v.parse::<f64>().is_ok()).unwrap_or(true));
110 if all_float {
111 return "float";
112 }
113 let all_bool = rows.iter().all(|r| {
114 r.get(ci)
115 .map(|v| *v == "true" || *v == "false")
116 .unwrap_or(true)
117 });
118 if all_bool {
119 return "bool";
120 }
121 "string"
122 })
123 .collect();
124
125 let mut ndjson = String::new();
127 for row in &rows {
128 let mut obj = serde_json::Map::new();
129 for (i, &col) in columns.iter().enumerate() {
130 if let Some(val) = row.get(i) {
131 let json_val = match col_types[i] {
132 "int" => val
133 .parse::<i64>()
134 .map(|n| serde_json::Value::Number(n.into()))
135 .unwrap_or(serde_json::Value::Null),
136 "float" => val
137 .parse::<f64>()
138 .ok()
139 .and_then(serde_json::Number::from_f64)
140 .map(serde_json::Value::Number)
141 .unwrap_or(serde_json::Value::Null),
142 "bool" => serde_json::Value::Bool(*val == "true"),
143 _ => serde_json::Value::String((*val).to_owned()),
144 };
145 obj.insert(col.to_string(), json_val);
146 }
147 }
148 if id_col.is_none() {
149 obj.insert(
150 "id".to_string(),
151 serde_json::Value::String(nodedb_types::id_gen::uuid_v7()),
152 );
153 }
154 let row_json =
155 serde_json::to_string(&obj).map_err(|e| NodeDbError::serialization("json", e))?;
156 ndjson.push_str(&row_json);
157 ndjson.push('\n');
158 }
159 self.copy_from_ndjson(collection, &ndjson).await
160 }
161}
162
163pub(crate) fn json_to_loro(v: &serde_json::Value) -> loro::LoroValue {
168 match v {
169 serde_json::Value::Null => loro::LoroValue::Null,
170 serde_json::Value::Bool(b) => loro::LoroValue::Bool(*b),
171 serde_json::Value::Number(n) => {
172 if let Some(i) = n.as_i64() {
173 loro::LoroValue::I64(i)
174 } else if let Some(f) = n.as_f64() {
175 loro::LoroValue::Double(f)
176 } else {
177 loro::LoroValue::String(n.to_string().into())
178 }
179 }
180 serde_json::Value::String(s) => loro::LoroValue::String(s.clone().into()),
181 serde_json::Value::Array(arr) => {
182 loro::LoroValue::List(arr.iter().map(json_to_loro).collect::<Vec<_>>().into())
183 }
184 serde_json::Value::Object(map) => loro::LoroValue::Map(
185 map.iter()
186 .map(|(k, v)| (k.to_string(), json_to_loro(v)))
187 .collect::<std::collections::HashMap<_, _>>()
188 .into(),
189 ),
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn json_to_loro_roundtrip() {
199 let json = serde_json::json!({
200 "name": "Alice",
201 "age": 30,
202 "active": true,
203 "score": 99.5,
204 "tags": ["a", "b"]
205 });
206 let loro = json_to_loro(&json);
207 assert!(matches!(loro, loro::LoroValue::Map(_)));
208 }
209}