1use crate::backend::StorageBackend;
5use crate::backend::table_names;
6use crate::backend::types::{FilterExpr, Scalar, ScanRequest, WriteMode};
7use anyhow::{Result, anyhow};
8use arrow_array::{ListArray, RecordBatch, UInt64Array};
9use arrow_schema::{DataType as ArrowDataType, Field, Schema as ArrowSchema};
10use std::collections::HashMap;
11use std::sync::Arc;
12use uni_common::core::id::{Eid, Vid};
13
14type AdjacencyLists = (Vec<Vid>, Vec<Eid>);
16
17type GroupedAdjacencyLists = HashMap<Vid, (Vec<Vid>, Vec<Eid>)>;
19
20fn downcast_adjacency_lists(batch: &RecordBatch) -> Result<(&ListArray, &ListArray)> {
22 let neighbors_list = batch
23 .column_by_name("neighbors")
24 .ok_or(anyhow!("Missing neighbors"))?
25 .as_any()
26 .downcast_ref::<ListArray>()
27 .ok_or(anyhow!("Invalid neighbors type"))?;
28
29 let edge_ids_list = batch
30 .column_by_name("edge_ids")
31 .ok_or(anyhow!("Missing edge_ids"))?
32 .as_any()
33 .downcast_ref::<ListArray>()
34 .ok_or(anyhow!("Invalid edge_ids type"))?;
35
36 Ok((neighbors_list, edge_ids_list))
37}
38
39fn extract_row_adjacency(
41 neighbors_list: &ListArray,
42 edge_ids_list: &ListArray,
43 row_idx: usize,
44) -> Result<(Vec<Vid>, Vec<Eid>)> {
45 let neighbors_array = neighbors_list.value(row_idx);
46 let neighbors_uint64 = neighbors_array
47 .as_any()
48 .downcast_ref::<UInt64Array>()
49 .ok_or(anyhow!("Invalid neighbors inner type"))?;
50
51 let edge_ids_array = edge_ids_list.value(row_idx);
52 let edge_ids_uint64 = edge_ids_array
53 .as_any()
54 .downcast_ref::<UInt64Array>()
55 .ok_or(anyhow!("Invalid edge_ids inner type"))?;
56
57 let neighbors = (0..neighbors_uint64.len())
58 .map(|i| Vid::from(neighbors_uint64.value(i)))
59 .collect();
60 let eids = (0..edge_ids_uint64.len())
61 .map(|i| Eid::from(edge_ids_uint64.value(i)))
62 .collect();
63
64 Ok((neighbors, eids))
65}
66
67fn extract_adjacency_from_batch(batch: &RecordBatch) -> Result<Option<AdjacencyLists>> {
71 if batch.num_rows() == 0 {
72 return Ok(None);
73 }
74
75 let (neighbors_list, edge_ids_list) = downcast_adjacency_lists(batch)?;
76
77 let mut all_neighbors = Vec::new();
78 let mut all_eids = Vec::new();
79
80 for row_idx in 0..batch.num_rows() {
81 let (neighbors, eids) = extract_row_adjacency(neighbors_list, edge_ids_list, row_idx)?;
82 all_neighbors.extend(neighbors);
83 all_eids.extend(eids);
84 }
85
86 Ok(Some((all_neighbors, all_eids)))
87}
88
89fn extract_adjacency_from_batch_grouped(batch: &RecordBatch) -> Result<GroupedAdjacencyLists> {
93 if batch.num_rows() == 0 {
94 return Ok(HashMap::new());
95 }
96
97 let src_vid_col = batch
98 .column_by_name("src_vid")
99 .ok_or(anyhow!("Missing src_vid"))?
100 .as_any()
101 .downcast_ref::<UInt64Array>()
102 .ok_or(anyhow!("Invalid src_vid type"))?;
103
104 let (neighbors_list, edge_ids_list) = downcast_adjacency_lists(batch)?;
105
106 let mut result: HashMap<Vid, (Vec<Vid>, Vec<Eid>)> = HashMap::new();
107
108 for row_idx in 0..batch.num_rows() {
109 let src_vid = Vid::from(src_vid_col.value(row_idx));
110 let (neighbors, eids) = extract_row_adjacency(neighbors_list, edge_ids_list, row_idx)?;
111 result.insert(src_vid, (neighbors, eids));
112 }
113
114 Ok(result)
115}
116
117pub struct AdjacencyDataset {
118 edge_type: String,
119 direction: String,
120 #[cfg_attr(not(feature = "lance-backend"), allow(dead_code))]
122 branch: Option<String>,
123}
124
125impl AdjacencyDataset {
126 pub fn new(_base_uri: &str, edge_type: &str, _label: &str, direction: &str) -> Self {
131 Self {
132 edge_type: edge_type.to_string(),
133 direction: direction.to_string(),
134 branch: None,
135 }
136 }
137
138 pub fn new_branched(
140 base_uri: &str,
141 edge_type: &str,
142 label: &str,
143 direction: &str,
144 branch: impl Into<String>,
145 ) -> Self {
146 let mut ds = Self::new(base_uri, edge_type, label, direction);
147 ds.branch = Some(branch.into());
148 ds
149 }
150
151 pub fn get_arrow_schema(&self) -> Arc<ArrowSchema> {
152 let fields = vec![
153 Field::new("src_vid", ArrowDataType::UInt64, false),
154 Field::new(
156 "neighbors",
157 ArrowDataType::List(Arc::new(Field::new("item", ArrowDataType::UInt64, true))),
158 false,
159 ),
160 Field::new(
162 "edge_ids",
163 ArrowDataType::List(Arc::new(Field::new("item", ArrowDataType::UInt64, true))),
164 false,
165 ),
166 ];
167
168 Arc::new(ArrowSchema::new(fields))
169 }
170
171 pub async fn read_adjacency_backend(
179 &self,
180 backend: &dyn StorageBackend,
181 vid: Vid,
182 ) -> Result<Option<(Vec<Vid>, Vec<Eid>)>> {
183 let table_name = table_names::adjacency_table_name(&self.edge_type, &self.direction);
184
185 if !backend.table_exists(&table_name).await? {
186 return Ok(None);
187 }
188
189 let filter = FilterExpr::equals("src_vid", Scalar::UInt(vid.as_u64()));
190 let batches = backend
191 .scan(ScanRequest::all(&table_name).with_filter(filter))
192 .await?;
193
194 for batch in batches {
195 if let Some(result) = extract_adjacency_from_batch(&batch)? {
196 return Ok(Some(result));
197 }
198 }
199
200 Ok(None)
201 }
202
203 pub async fn read_adjacency_backend_batch(
208 &self,
209 backend: &dyn StorageBackend,
210 vids: &[Vid],
211 ) -> Result<HashMap<Vid, (Vec<Vid>, Vec<Eid>)>> {
212 if vids.is_empty() {
213 return Ok(HashMap::new());
214 }
215
216 let table_name = table_names::adjacency_table_name(&self.edge_type, &self.direction);
217
218 if !backend.table_exists(&table_name).await? {
219 return Ok(HashMap::new());
220 }
221
222 let filter = FilterExpr::one_of("src_vid", vids.iter().map(|v| Scalar::UInt(v.as_u64())));
223 let batches = backend
224 .scan(ScanRequest::all(&table_name).with_filter(filter))
225 .await?;
226
227 let mut result = HashMap::new();
228 for batch in batches {
229 let batch_result = extract_adjacency_from_batch_grouped(&batch)?;
230 result.extend(batch_result);
231 }
232
233 Ok(result)
234 }
235
236 pub async fn open_or_create(&self, backend: &dyn StorageBackend) -> Result<()> {
238 let table_name = table_names::adjacency_table_name(&self.edge_type, &self.direction);
239 let arrow_schema = self.get_arrow_schema();
240 backend
241 .open_or_create_table(&table_name, arrow_schema)
242 .await
243 }
244
245 pub async fn write_chunk(
249 &self,
250 backend: &dyn StorageBackend,
251 batch: RecordBatch,
252 ) -> Result<()> {
253 let table_name = table_names::adjacency_table_name(&self.edge_type, &self.direction);
254 if backend.table_exists(&table_name).await? {
255 backend
256 .write(&table_name, vec![batch], WriteMode::Append)
257 .await
258 } else {
259 backend.create_table(&table_name, vec![batch]).await
260 }
261 }
262
263 pub fn table_name(&self) -> String {
265 table_names::adjacency_table_name(&self.edge_type, &self.direction)
266 }
267
268 pub async fn replace(&self, backend: &dyn StorageBackend, batch: RecordBatch) -> Result<()> {
272 let table_name = self.table_name();
273 let arrow_schema = self.get_arrow_schema();
274 backend
275 .replace_table_atomic(&table_name, vec![batch], arrow_schema)
276 .await
277 }
278}