linkrs_query/planning/statements/seeks/
edge_seek.rs1use super::seek_strategy::SeekStrategy;
11use super::seek_strategy_base::{SeekResult, SeekStrategyContext, SeekStrategyType};
12use crate::storage::StorageReader;
13use linkrs_core::{StorageError, Value};
14use std::sync::Arc;
15
16#[derive(Debug, Clone, PartialEq)]
18pub struct EdgePattern {
19 pub edge_types: Vec<String>,
20 pub direction: EdgeDirection,
21 pub src_vid: Option<Value>,
22 pub dst_vid: Option<Value>,
23 pub properties: Vec<(Arc<str>, Value)>,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum EdgeDirection {
29 Outgoing, Incoming, Both, }
33
34impl EdgeDirection {
35 pub fn as_str(&self) -> &'static str {
36 match self {
37 EdgeDirection::Outgoing => "OUT",
38 EdgeDirection::Incoming => "IN",
39 EdgeDirection::Both => "BOTH",
40 }
41 }
42}
43
44impl std::str::FromStr for EdgeDirection {
45 type Err = String;
46
47 fn from_str(s: &str) -> Result<Self, Self::Err> {
48 match s.to_uppercase().as_str() {
49 "OUT" | "OUTGOING" | "->" => Ok(EdgeDirection::Outgoing),
50 "IN" | "INCOMING" | "<-" => Ok(EdgeDirection::Incoming),
51 "BOTH" | "-" => Ok(EdgeDirection::Both),
52 _ => Err(format!("Invalid edge direction: {}", s)),
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
59pub struct EdgeSeek {
60 pub edge_pattern: EdgePattern,
61}
62
63impl EdgeSeek {
64 pub fn new(edge_pattern: EdgePattern) -> Self {
65 Self { edge_pattern }
66 }
67
68 fn edge_matches_pattern(&self, edge: &linkrs_core::Edge) -> bool {
70 if !self.edge_pattern.edge_types.is_empty()
72 && !self.edge_pattern.edge_types.contains(&edge.edge_type)
73 {
74 return false;
75 }
76
77 if let Some(ref src_vid) = self.edge_pattern.src_vid {
78 if Value::from(edge.src) != *src_vid {
79 return false;
80 }
81 }
82
83 if let Some(ref dst_vid) = self.edge_pattern.dst_vid {
84 if Value::from(edge.dst) != *dst_vid {
85 return false;
86 }
87 }
88
89 for (prop_name, prop_value) in &self.edge_pattern.properties {
91 let found = edge
92 .get_property(prop_name)
93 .map(|v| v == prop_value)
94 .unwrap_or(false);
95 if !found {
96 return false;
97 }
98 }
99
100 true
101 }
102}
103
104impl SeekStrategy for EdgeSeek {
105 fn execute<S: StorageReader>(
106 &self,
107 storage: &S,
108 _context: &SeekStrategyContext,
109 ) -> Result<SeekResult, StorageError> {
110 let mut edge_ids = Vec::new();
111 let mut rows_scanned = 0;
112
113 let space_name = "default";
114
115 if !self.edge_pattern.edge_types.is_empty() {
117 for edge_type in &self.edge_pattern.edge_types {
118 let edges = storage.scan_edges_by_type(space_name, edge_type)?;
119 rows_scanned += edges.len();
120
121 for edge in edges {
122 let edge_id = format!("{}->{}@{}", edge.src, edge.dst, edge.edge_type);
123 edge_ids.push(Value::string(edge_id));
124 }
125 }
126 } else {
127 let edges = storage.scan_all_edges(space_name)?;
129 rows_scanned = edges.len();
130
131 for edge in edges {
132 if self.edge_matches_pattern(&edge) {
133 let edge_id = format!("{}->{}@{}", edge.src, edge.dst, edge.edge_type);
134 edge_ids.push(Value::string(edge_id));
135 }
136 }
137 }
138
139 edge_ids.sort();
141 edge_ids.dedup();
142
143 Ok(SeekResult {
144 vertex_ids: edge_ids,
145 strategy_used: SeekStrategyType::EdgeSeek,
146 rows_scanned,
147 })
148 }
149
150 fn supports(&self, _context: &SeekStrategyContext) -> bool {
151 true
153 }
154}
155
156#[derive(Debug)]
158pub struct EdgeSeekResult {
159 pub base: SeekResult,
160 pub start_vids: Vec<Value>,
161 pub end_vids: Vec<Value>,
162}
163
164impl EdgeSeekResult {
165 pub fn from_seek_result(
166 result: SeekResult,
167 start_vids: Vec<Value>,
168 end_vids: Vec<Value>,
169 ) -> Self {
170 Self {
171 base: result,
172 start_vids,
173 end_vids,
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use linkrs_core::types::VertexId;
182
183 #[test]
184 fn test_edge_direction_from_str() {
185 assert_eq!(
186 "OUT".parse::<EdgeDirection>().ok(),
187 Some(EdgeDirection::Outgoing)
188 );
189 assert_eq!(
190 "->".parse::<EdgeDirection>().ok(),
191 Some(EdgeDirection::Outgoing)
192 );
193 assert_eq!(
194 "IN".parse::<EdgeDirection>().ok(),
195 Some(EdgeDirection::Incoming)
196 );
197 assert_eq!(
198 "<-".parse::<EdgeDirection>().ok(),
199 Some(EdgeDirection::Incoming)
200 );
201 assert_eq!(
202 "BOTH".parse::<EdgeDirection>().ok(),
203 Some(EdgeDirection::Both)
204 );
205 assert_eq!("-".parse::<EdgeDirection>().ok(), Some(EdgeDirection::Both));
206 assert!("unknown".parse::<EdgeDirection>().is_err());
207 }
208
209 #[test]
210 fn test_edge_pattern_matching() {
211 let seek = EdgeSeek::new(EdgePattern {
212 edge_types: vec!["KNOWS".to_string()],
213 direction: EdgeDirection::Outgoing,
214 src_vid: None,
215 dst_vid: None,
216 properties: vec![],
217 });
218
219 let edge = linkrs_core::Edge::new_empty(
221 VertexId::try_from_int64(1).expect("valid vertex id"),
222 VertexId::try_from_int64(2).expect("valid vertex id"),
223 "KNOWS".to_string(),
224 0,
225 );
226 assert!(seek.edge_matches_pattern(&edge));
227
228 let edge2 = linkrs_core::Edge::new_empty(
230 VertexId::try_from_int64(1).expect("valid vertex id"),
231 VertexId::try_from_int64(2).expect("valid vertex id"),
232 "FOLLOWS".to_string(),
233 0,
234 );
235 assert!(!seek.edge_matches_pattern(&edge2));
236 }
237
238 #[test]
239 fn test_edge_pattern_with_src_vid() {
240 let seek = EdgeSeek::new(EdgePattern {
241 edge_types: vec![],
242 direction: EdgeDirection::Outgoing,
243 src_vid: Some(Value::Int(1)),
244 dst_vid: None,
245 properties: vec![],
246 });
247
248 let edge = linkrs_core::Edge::new_empty(
249 VertexId::try_from_int64(1).expect("valid vertex id"),
250 VertexId::try_from_int64(2).expect("valid vertex id"),
251 "KNOWS".to_string(),
252 0,
253 );
254 assert!(seek.edge_matches_pattern(&edge));
255
256 let edge2 = linkrs_core::Edge::new_empty(
257 VertexId::try_from_int64(3).expect("valid vertex id"),
258 VertexId::try_from_int64(2).expect("valid vertex id"),
259 "KNOWS".to_string(),
260 0,
261 );
262 assert!(!seek.edge_matches_pattern(&edge2));
263 }
264}