1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4 sync::Arc,
5};
6
7use arrow_array::{Int64Array, RecordBatch};
8use arrow_schema::{DataType as ArrowDataType, Field, Schema};
9
10use crate::{
11 error::Result,
12 io,
13 metadata::{EdgeInfo, PropertyGroup},
14 types::AdjListType,
15 writer::vertex_writer::{PropertyValue, build_record_batch_for_edges},
16};
17
18#[derive(Debug, Clone)]
20pub struct Edge {
21 pub src: i64,
22 pub dst: i64,
23 properties: HashMap<String, PropertyValue>,
24}
25
26impl Edge {
27 pub fn new(src: i64, dst: i64) -> Self {
28 Self {
29 src,
30 dst,
31 properties: HashMap::new(),
32 }
33 }
34
35 pub fn add_property(mut self, name: impl Into<String>, value: PropertyValue) -> Self {
36 self.properties.insert(name.into(), value);
37 self
38 }
39
40 pub fn get(&self, name: &str) -> Option<&PropertyValue> {
41 self.properties.get(name)
42 }
43}
44
45pub struct EdgesBuilder {
47 info: EdgeInfo,
48 base_dir: PathBuf,
49 adj_type: AdjListType,
50 vertex_count: u64,
52 edges: Vec<Edge>,
53}
54
55impl EdgesBuilder {
56 pub fn new(
57 info: EdgeInfo,
58 base_dir: impl AsRef<Path>,
59 adj_type: AdjListType,
60 vertex_count: u64,
61 ) -> Self {
62 Self {
63 info,
64 base_dir: base_dir.as_ref().to_path_buf(),
65 adj_type,
66 vertex_count,
67 edges: Vec::new(),
68 }
69 }
70
71 pub fn add_edge(&mut self, edge: Edge) {
72 self.edges.push(edge);
73 }
74
75 pub fn dump(&mut self) -> Result<()> {
77 let aligned_by_src = self.adj_type.aligned_by_src();
85 let key = |e: &Edge| -> u64 {
86 if aligned_by_src {
87 e.src as u64
88 } else {
89 e.dst as u64
90 }
91 };
92 self.edges.sort_unstable_by_key(key);
93
94 let vchunk_size = if aligned_by_src {
95 self.info.src_chunk_size
96 } else {
97 self.info.dst_chunk_size
98 };
99 let num_vertex_chunks = self.vertex_count.div_ceil(vchunk_size);
100
101 let file_type = self
102 .info
103 .get_adj_list_info(&self.adj_type)
104 .map(|a| a.file_type.clone())
105 .unwrap_or_default();
106
107 let mut cursor = 0usize;
118
119 for vc in 0..num_vertex_chunks {
120 let v_start = vc * vchunk_size;
121 let v_end = ((vc + 1) * vchunk_size).min(self.vertex_count);
122
123 debug_assert!(self.edges[cursor..].iter().all(|e| key(e) >= v_start));
127 let chunk_end = cursor + self.edges[cursor..].partition_point(|e| key(e) < v_end);
128 let chunk_edges: Vec<&Edge> = self.edges[cursor..chunk_end].iter().collect();
129 cursor = chunk_end;
130
131 let edge_chunk_size = self.info.chunk_size as usize;
133 for (ec, edge_slice) in chunk_edges.chunks(edge_chunk_size).enumerate() {
134 let batch = build_adj_list_batch(edge_slice)?;
135 let path = self.base_dir.join(self.info.adj_list_chunk_path(
136 &self.adj_type,
137 vc,
138 ec as u64,
139 )?);
140 io::write_chunk(&path, &[batch], &file_type)?;
141
142 for group in self.info.property_groups.iter() {
144 let prop_batch = build_edge_prop_batch(edge_slice, group)?;
145 let prop_path = self.base_dir.join(self.info.property_chunk_path(
146 &self.adj_type,
147 group,
148 vc,
149 ec as u64,
150 ));
151 io::write_chunk(&prop_path, &[prop_batch], &group.file_type)?;
152 }
153 }
154
155 if self.adj_type.is_ordered() {
157 let offset_batch =
158 build_offset_batch(&chunk_edges, v_start, v_end, vchunk_size, aligned_by_src)?;
159 let offset_path = self
160 .base_dir
161 .join(self.info.offset_chunk_path(&self.adj_type, vc)?);
162 io::write_chunk(&offset_path, &[offset_batch], &file_type)?;
163 }
164 }
165
166 self.edges.clear();
167 Ok(())
168 }
169}
170
171fn build_adj_list_batch(edges: &[&Edge]) -> Result<RecordBatch> {
172 let srcs: Vec<i64> = edges.iter().map(|e| e.src).collect();
173 let dsts: Vec<i64> = edges.iter().map(|e| e.dst).collect();
174 let schema = Arc::new(Schema::new(vec![
175 Field::new("src", ArrowDataType::Int64, false),
176 Field::new("dst", ArrowDataType::Int64, false),
177 ]));
178 Ok(RecordBatch::try_new(
179 schema,
180 vec![
181 Arc::new(Int64Array::from(srcs)),
182 Arc::new(Int64Array::from(dsts)),
183 ],
184 )?)
185}
186
187fn build_offset_batch(
188 chunk_edges: &[&Edge],
189 v_start: u64,
190 v_end: u64,
191 _vchunk_size: u64,
192 aligned_by_src: bool,
193) -> Result<RecordBatch> {
194 let count = (v_end - v_start) as usize + 1;
195 let mut offsets = vec![0i64; count];
196 let mut hist = vec![0i64; (v_end - v_start) as usize];
198 for edge in chunk_edges {
199 let key_id = if aligned_by_src { edge.src } else { edge.dst };
204 let slot = (key_id as u64).saturating_sub(v_start) as usize;
205 if slot < hist.len() {
206 hist[slot] += 1;
207 }
208 }
209 let mut running = 0i64;
211 offsets[0] = 0;
212 for (i, count) in hist.iter().enumerate() {
213 running += count;
214 offsets[i + 1] = running;
215 }
216
217 let schema = Arc::new(Schema::new(vec![Field::new(
218 "offset",
219 ArrowDataType::Int64,
220 false,
221 )]));
222 Ok(RecordBatch::try_new(
223 schema,
224 vec![Arc::new(Int64Array::from(offsets))],
225 )?)
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use arrow_array::Array;
232
233 fn offsets_of(batch: &RecordBatch) -> Vec<i64> {
234 batch
235 .column(0)
236 .as_any()
237 .downcast_ref::<Int64Array>()
238 .unwrap()
239 .values()
240 .to_vec()
241 }
242
243 #[test]
244 fn offset_batch_keys_on_dst_for_ordered_by_dest() {
245 let edges = vec![
253 Edge::new(100, 0),
254 Edge::new(100, 0),
255 Edge::new(100, 1),
256 Edge::new(100, 2),
257 ];
258 let refs: Vec<&Edge> = edges.iter().collect();
259
260 let batch = build_offset_batch(&refs, 0, 3, 4, false).unwrap();
262 assert_eq!(
263 offsets_of(&batch),
264 vec![0, 2, 3, 4],
265 "ordered_by_dest offsets must be the dst-keyed prefix sum",
266 );
267
268 let buggy = build_offset_batch(&refs, 0, 3, 4, true).unwrap();
270 assert_eq!(offsets_of(&buggy), vec![0, 0, 0, 0]);
271 }
272
273 #[test]
274 fn offset_batch_keys_on_src_for_ordered_by_source() {
275 let edges = vec![Edge::new(0, 9), Edge::new(1, 9), Edge::new(1, 9)];
277 let refs: Vec<&Edge> = edges.iter().collect();
278 let batch = build_offset_batch(&refs, 0, 2, 4, true).unwrap();
279 assert_eq!(offsets_of(&batch), vec![0, 1, 3]);
280 }
281}
282
283fn build_edge_prop_batch(edges: &[&Edge], group: &PropertyGroup) -> Result<RecordBatch> {
284 let pseudo_vertices: Vec<crate::writer::vertex_writer::Vertex> = edges
286 .iter()
287 .map(|e| {
288 let mut v = crate::writer::vertex_writer::Vertex::new();
289 for prop in &group.properties {
290 if let Some(val) = e.get(&prop.name) {
291 v = v.add_property(prop.name.clone(), val.clone());
292 }
293 }
294 v
295 })
296 .collect();
297
298 build_record_batch_for_edges(&pseudo_vertices, group)
299}