1use crate::planning::plan::core::nodes::base::plan_node_traits::{
4 MultipleInputNode, SingleInputNode,
5};
6use crate::planning::plan::PlanNodeEnum;
7use linkrs_core::types::ContextualExpression;
8use linkrs_core::Expression;
9
10use super::map::RequiredPropertiesMap;
11use super::ref_collector::CollectedRefs;
12use super::requirement::PropertyRequirement;
13
14#[derive(Debug, Clone, Default)]
16pub struct RequiredPropertyAnalyzer;
17
18impl RequiredPropertyAnalyzer {
19 pub fn new() -> Self {
20 Self
21 }
22
23 pub fn analyze(&self, root: &PlanNodeEnum) -> RequiredPropertiesMap {
31 let mut map = RequiredPropertiesMap::default();
32 self.propagate(root, &[], &mut map);
33 map
34 }
35
36 fn propagate(
38 &self,
39 node: &PlanNodeEnum,
40 incoming: &[PropertyRequirement],
41 map: &mut RequiredPropertiesMap,
42 ) {
43 use PlanNodeEnum::*;
44
45 match node {
48 GetVertices(n) => {
49 self.record_leaf(
50 map,
51 node.id(),
52 incoming,
53 binding_var(n.output_var(), n.src_vids()),
54 single_tag(n.tag_props()),
55 );
56 return;
57 }
58 GetNeighbors(n) => {
59 self.record_leaf(
60 map,
61 node.id(),
62 incoming,
63 binding_var(n.output_var(), n.src_vids()),
64 single_tag(n.tag_props()),
65 );
66 return;
67 }
68 ScanVertices(n) => {
69 self.record_leaf(
70 map,
71 node.id(),
72 incoming,
73 n.col_names().first().map(String::as_str),
74 n.tag().map(|t| t.to_string()),
75 );
76 return;
77 }
78 ScanEdges(n) => {
79 self.record_leaf(
80 map,
81 node.id(),
82 incoming,
83 n.col_names().first().map(String::as_str),
84 n.edge_type().map(|e| e.to_string()),
85 );
86 return;
87 }
88 GetEdges(n) => {
89 self.record_leaf(
90 map,
91 node.id(),
92 incoming,
93 Some(n.src()),
94 Some(n.edge_type().to_string()),
95 );
96 return;
97 }
98 AppendVertices(n) => {
99 self.record_leaf(
100 map,
101 node.id(),
102 incoming,
103 n.input_var(),
104 Some(n.vertex_tag().to_string()),
105 );
106 return;
107 }
108 IndexScan(_) | Start(_) | Argument(_) => {
109 map.record(node.id(), incoming);
110 return;
111 }
112 _ => {}
113 }
114
115 map.record(node.id(), incoming);
119 let mut passed: Vec<PropertyRequirement> = Vec::new();
120 for req in incoming {
121 passed.push(req.clone());
122 }
123
124 match node {
125 Project(n) => {
126 for col in n.columns() {
127 if let Some(meta) = col.expression.expression() {
128 merge_collected(meta.inner(), &mut passed);
129 }
130 }
131 for col in n.columns() {
134 if let Some(meta) = col.expression.expression() {
135 if let Expression::Variable(var) = meta.inner() {
136 if let Some(req) = incoming.iter().find(|r| r.alias == *var) {
137 merge_requirement(&mut passed, req);
138 }
139 }
140 }
141 }
142 self.propagate(n.input(), &passed, map);
143 }
144 Filter(n) => {
145 merge_expr(n.condition(), &mut passed);
146 self.propagate(n.input(), &passed, map);
147 }
148 Sort(n) => self.propagate_single(n.input(), &passed, map),
149 Limit(n) => self.propagate_single(n.input(), &passed, map),
150 TopN(n) => self.propagate_single(n.input(), &passed, map),
151 Sample(n) => self.propagate_single(n.input(), &passed, map),
152 Dedup(n) => self.propagate_single(n.input(), &passed, map),
153 Aggregate(n) => self.propagate_single(n.input(), &passed, map),
154 Window(n) => self.propagate_single(n.input(), &passed, map),
155 Unwind(n) => self.propagate_single(n.input(), &passed, map),
156 Materialize(n) => self.propagate_single(n.input(), &passed, map),
157 DataCollect(n) => self.propagate_single(n.input(), &passed, map),
158
159 InnerJoin(n) => {
163 Self::merge_join_keys(n.hash_keys(), n.probe_keys(), &mut passed);
164 self.propagate(n.left_input(), &passed, map);
165 self.propagate(n.right_input(), &passed, map);
166 }
167 LeftJoin(n) => {
168 Self::merge_join_keys(n.hash_keys(), n.probe_keys(), &mut passed);
169 self.propagate(n.left_input(), &passed, map);
170 self.propagate(n.right_input(), &passed, map);
171 }
172 FullOuterJoin(n) => {
173 Self::merge_join_keys(n.hash_keys(), n.probe_keys(), &mut passed);
174 self.propagate(n.left_input(), &passed, map);
175 self.propagate(n.right_input(), &passed, map);
176 }
177 RightJoin(n) => {
178 Self::merge_join_keys(n.hash_keys(), n.probe_keys(), &mut passed);
179 self.propagate(n.left_input(), &passed, map);
180 self.propagate(n.right_input(), &passed, map);
181 }
182 SemiJoin(n) => {
183 Self::merge_join_keys(n.hash_keys(), n.probe_keys(), &mut passed);
184 self.propagate(n.left_input(), &passed, map);
185 self.propagate(n.right_input(), &passed, map);
186 }
187 CrossJoin(n) => {
188 self.propagate(n.left_input(), &passed, map);
189 self.propagate(n.right_input(), &passed, map);
190 }
191
192 Union(n) => {
194 for dep in n.dependencies() {
195 self.propagate(dep, &passed, map);
196 }
197 }
198 Minus(n) => {
199 self.propagate(n.input(), &passed, map);
200 self.propagate(n.minus_input(), &passed, map);
201 }
202 Intersect(n) => {
203 self.propagate(n.input(), &passed, map);
204 self.propagate(n.intersect_input(), &passed, map);
205 }
206
207 Expand(n) => {
209 for dep in n.inputs() {
210 self.propagate(dep, &passed, map);
211 }
212 }
213 ExpandAll(n) => {
214 for dep in n.inputs() {
215 self.propagate(dep, &passed, map);
216 }
217 }
218 Traverse(n) => {
219 for dep in n.dependencies() {
220 self.propagate(dep, &passed, map);
221 }
222 }
223 BiExpand(n) => {
224 self.propagate(n.left_input(), &passed, map);
225 self.propagate(n.right_input(), &passed, map);
226 }
227 BiTraverse(n) => {
228 self.propagate(n.left_input(), &passed, map);
229 self.propagate(n.right_input(), &passed, map);
230 }
231
232 PatternApply(n) => {
236 self.propagate(n.left_input(), &passed, map);
237 self.propagate(n.right_input(), &[], map);
238 }
239 CorrelatedApply(n) => {
240 self.propagate(n.left_input(), &passed, map);
241 self.propagate(n.right_input(), &[], map);
242 }
243 RollUpApply(n) => {
244 self.propagate(n.left_input(), &passed, map);
245 self.propagate(n.right_input(), &[], map);
246 }
247
248 Loop(n) => {
250 if let Some(body) = n.body() {
251 self.propagate(body, &passed, map);
252 }
253 }
254 Select(n) => {
255 if let Some(branch) = n.if_branch() {
256 self.propagate(branch, &passed, map);
257 }
258 if let Some(branch) = n.else_branch() {
259 self.propagate(branch, &passed, map);
260 }
261 }
262
263 ShortestPath(n) => {
265 self.propagate(n.left_input(), &passed, map);
266 self.propagate(n.right_input(), &passed, map);
267 }
268 MultiShortestPath(n) => {
269 self.propagate(n.left_input(), &passed, map);
270 self.propagate(n.right_input(), &passed, map);
271 }
272 BFSShortest(n) => {
273 self.propagate(n.left_input(), &passed, map);
274 self.propagate(n.right_input(), &passed, map);
275 }
276 AllPaths(n) => {
277 self.propagate(n.left_input(), &passed, map);
278 self.propagate(n.right_input(), &passed, map);
279 }
280
281 Assign(n) => {
283 for dep in n.dependencies() {
284 self.propagate(dep, &passed, map);
285 }
286 }
287
288 _ => {}
291 }
292 }
293
294 fn propagate_single(
295 &self,
296 input: &PlanNodeEnum,
297 passed: &[PropertyRequirement],
298 map: &mut RequiredPropertiesMap,
299 ) {
300 self.propagate(input, passed, map);
301 }
302
303 fn merge_join_keys(
306 hash_keys: &[ContextualExpression],
307 probe_keys: &[ContextualExpression],
308 passed: &mut Vec<PropertyRequirement>,
309 ) {
310 for key in hash_keys.iter().chain(probe_keys) {
311 merge_expr(key, passed);
312 }
313 }
314
315 fn record_leaf(
318 &self,
319 map: &mut RequiredPropertiesMap,
320 node_id: i64,
321 incoming: &[PropertyRequirement],
322 binding_var: Option<&str>,
323 tag_name: Option<String>,
324 ) {
325 let Some(var) = binding_var else {
326 return;
327 };
328 let mut resolved: Vec<PropertyRequirement> = Vec::new();
329 for req in incoming {
330 if req.alias == var {
331 let mut tagged = req.clone();
332 tagged.tag_name = tag_name.clone();
333 resolved.push(tagged);
334 } else if let Some(tag) = tag_name.as_deref() {
335 if req.alias == tag {
340 let mut remapped = req.clone();
341 remapped.alias = var.to_string();
342 remapped.tag_name = tag_name.clone();
343 resolved.push(remapped);
344 }
345 }
346 }
347 if !resolved.is_empty() {
348 map.record(node_id, &resolved);
349 }
350 }
351}
352
353pub fn binding_var<'a>(output_var: Option<&'a str>, src_vids: &'a str) -> Option<&'a str> {
359 if let Some(var) = output_var {
360 if !var.is_empty() {
361 return Some(var);
362 }
363 }
364 let trimmed = src_vids.trim();
365 if trimmed.is_empty() || trimmed.split(',').count() != 1 {
366 return None;
367 }
368 if trimmed.parse::<i64>().is_ok() {
370 return None;
371 }
372 Some(trimmed)
373}
374
375fn single_tag(tag_props: &[crate::planning::plan::core::common::TagProp]) -> Option<String> {
377 let mut tags = tag_props
378 .iter()
379 .map(|tp| tp.tag.clone())
380 .collect::<Vec<_>>();
381 tags.sort();
382 tags.dedup();
383 if tags.len() == 1 {
384 tags.pop()
385 } else {
386 None
387 }
388}
389
390fn merge_collected(expr: &Expression, passed: &mut Vec<PropertyRequirement>) {
393 let collected = CollectedRefs::visit(expr);
394 for (var, prop) in collected.props {
395 merge_property(passed, &var, &prop);
396 }
397 for var in collected.full_vars {
398 merge_property(passed, &var, "");
401 }
402}
403
404fn merge_expr(expr: &ContextualExpression, passed: &mut Vec<PropertyRequirement>) {
407 let Some(meta) = expr.expression() else {
408 return;
409 };
410 merge_collected(meta.inner(), passed);
411}
412
413fn merge_property(passed: &mut Vec<PropertyRequirement>, var: &str, prop: &str) {
417 if let Some(req) = passed.iter_mut().find(|r| r.alias == var) {
418 if prop.is_empty() {
419 req.full_value = true;
420 } else {
421 req.prop_names.insert(prop.to_string());
422 }
423 } else {
424 let mut req = PropertyRequirement::new(var);
425 if prop.is_empty() {
426 req.full_value = true;
427 } else {
428 req.prop_names.insert(prop.to_string());
429 }
430 passed.push(req);
431 }
432}
433
434fn merge_requirement(passed: &mut Vec<PropertyRequirement>, req: &PropertyRequirement) {
436 match passed.iter_mut().find(|r| r.alias == req.alias) {
437 Some(existing) => existing.merge(req),
438 None => passed.push(req.clone()),
439 }
440}