linkrs_storage/client/
query_storage.rs1use super::import_export::{format_csv_value, import_space_impl};
2use super::{CatalogStore, GraphStore, StorageAdmin, StorageAuthOps, StoragePersistenceOps};
3use crate::stats_reader::ColumnStatsReader;
4use crate::{AutoCommitBatchOps, AutoCommitGroupOps, SnapshotHandle};
5use linkrs_core::StorageError;
6use std::io::Write;
7
8pub trait QueryStorage:
10 GraphStore
11 + CatalogStore
12 + StorageAuthOps
13 + StorageAdmin
14 + StoragePersistenceOps
15 + ColumnStatsReader
16 + AutoCommitBatchOps
17 + AutoCommitGroupOps
18{
19 fn snapshot_handle(&self) -> Option<SnapshotHandle> {
35 let context = self.operation_context()?;
36 Some(SnapshotHandle::new(context.read_timestamp, 0))
37 }
38
39 fn export_space(&self, space: &str, path: &std::path::Path) -> Result<(), StorageError> {
45 let base = path.join(space);
46 std::fs::create_dir_all(&base)
47 .map_err(|e| StorageError::io_error(format!("Failed to create export dir: {e}")))?;
48
49 let tags = self.list_tags(space)?;
51 let edge_types = self.list_edge_types(space)?;
52
53 let schema_meta = serde_json::json!({
54 "space": space,
55 "tags": tags.iter().map(|t| serde_json::json!({
56 "name": t.tag_name,
57 "properties": t.properties.iter().map(|p| serde_json::json!({
58 "name": p.name,
59 "type": p.data_type.to_string(),
60 })).collect::<Vec<_>>(),
61 })).collect::<Vec<_>>(),
62 "edge_types": edge_types.iter().map(|e| serde_json::json!({
63 "name": e.edge_type_name,
64 "src_tag": e.src_tag_name,
65 "dst_tag": e.dst_tag_name,
66 "properties": e.properties.iter().map(|p| serde_json::json!({
67 "name": p.name,
68 "type": p.data_type.to_string(),
69 })).collect::<Vec<_>>(),
70 })).collect::<Vec<_>>(),
71 });
72
73 let schema_path = base.join("schema.json");
74 let mut schema_file = std::fs::File::create(&schema_path)
75 .map_err(|e| StorageError::io_error(format!("Failed to create schema.json: {e}")))?;
76 schema_file
77 .write_all(
78 serde_json::to_string_pretty(&schema_meta)
79 .unwrap_or_default()
80 .as_bytes(),
81 )
82 .map_err(|e| StorageError::io_error(format!("Failed to write schema.json: {e}")))?;
83
84 for tag_info in &tags {
86 let vertices = self.scan_vertices_by_tag(space, &tag_info.tag_name)?;
87 if vertices.is_empty() {
88 continue;
89 }
90
91 let mut prop_keys: Vec<std::sync::Arc<str>> = vertices
93 .iter()
94 .flat_map(|v| v.tag.properties.keys())
95 .cloned()
96 .collect::<std::collections::HashSet<_>>()
97 .into_iter()
98 .collect();
99 prop_keys.sort();
100
101 let csv_path = base.join(format!("{}.csv", tag_info.tag_name));
102 let mut file = std::fs::File::create(&csv_path).map_err(|e| {
103 StorageError::io_error(format!("Failed to create {}.csv: {e}", tag_info.tag_name))
104 })?;
105
106 let mut header = vec!["vid".to_string()];
108 header.extend(prop_keys.iter().map(|k| k.to_string()));
109 writeln!(file, "{}", header.join(","))
110 .map_err(|e| StorageError::io_error(format!("CSV write error: {e}")))?;
111
112 for vertex in &vertices {
114 let mut row = vec![vertex.vid.to_string()];
115 for key in &prop_keys {
116 let val = vertex
117 .property_value(key)
118 .map(|value| format_csv_value(&value))
119 .unwrap_or_default();
120 row.push(val);
121 }
122 writeln!(file, "{}", row.join(","))
123 .map_err(|e| StorageError::io_error(format!("CSV write error: {e}")))?;
124 }
125 }
126
127 for edge_info in &edge_types {
129 let edges = self.scan_edges_by_type(space, &edge_info.edge_type_name)?;
130 if edges.is_empty() {
131 continue;
132 }
133
134 let mut prop_keys: Vec<std::sync::Arc<str>> = edges
135 .iter()
136 .flat_map(|e| e.props.keys())
137 .cloned()
138 .collect::<std::collections::HashSet<_>>()
139 .into_iter()
140 .collect();
141 prop_keys.sort();
142
143 let csv_path = base.join(format!("{}.csv", edge_info.edge_type_name));
144 let mut file = std::fs::File::create(&csv_path).map_err(|e| {
145 StorageError::io_error(format!(
146 "Failed to create {}.csv: {e}",
147 edge_info.edge_type_name
148 ))
149 })?;
150
151 let mut header = vec!["src".to_string(), "dst".to_string(), "ranking".to_string()];
152 header.extend(prop_keys.iter().map(|k| k.to_string()));
153 writeln!(file, "{}", header.join(","))
154 .map_err(|e| StorageError::io_error(format!("CSV write error: {e}")))?;
155
156 for edge in &edges {
157 let mut row = vec![
158 edge.src.to_string(),
159 edge.dst.to_string(),
160 edge.ranking.to_string(),
161 ];
162 for key in &prop_keys {
163 let val = edge
164 .props
165 .get(key.as_ref())
166 .map(format_csv_value)
167 .unwrap_or_default();
168 row.push(val);
169 }
170 writeln!(file, "{}", row.join(","))
171 .map_err(|e| StorageError::io_error(format!("CSV write error: {e}")))?;
172 }
173 }
174
175 Ok(())
176 }
177
178 fn import_space(&mut self, space: &str, path: &std::path::Path) -> Result<(), StorageError> {
182 import_space_impl(self, space, path)
183 }
184}
185
186impl<T> QueryStorage for T where
187 T: GraphStore
188 + CatalogStore
189 + StorageAuthOps
190 + StorageAdmin
191 + StoragePersistenceOps
192 + ColumnStatsReader
193 + AutoCommitBatchOps
194 + AutoCommitGroupOps
195{
196}