1mod access;
2mod assign;
3mod control_flow;
4mod flat_leaf;
5mod join;
6mod operation;
7mod set_ops;
8mod traversal;
9mod unwind;
10
11use linkrs_core::types::expr::contextual::ContextualExpression;
12use linkrs_core::types::expr::ExpressionId;
13
14use crate::planning::plan::factorization::{
15 FGroupPos, FactorizationError, FactorizedSchema, FactorizedSchemaCompute,
16};
17
18use crate::planning::plan::logical::logical_node_enum::LogicalNodeEnum;
19
20pub(super) fn resolve_id(expr: &ContextualExpression) -> ExpressionId {
21 expr.id().clone()
22}
23
24pub(super) fn register_output_names(
33 schema: &mut FactorizedSchema,
34 output_var: Option<&str>,
35 col_names: &[String],
36 group: FGroupPos,
37) -> Result<(), FactorizationError> {
38 if let Some(var) = output_var {
39 schema.insert_name_for_group(var.to_string(), group)?;
40 }
41 for name in col_names {
42 schema.insert_name_for_group(name.clone(), group)?;
43 }
44 Ok(())
45}
46
47fn pair(child_schemas: &[FactorizedSchema]) -> (&FactorizedSchema, &FactorizedSchema) {
50 static EMPTY: std::sync::OnceLock<FactorizedSchema> = std::sync::OnceLock::new();
51 let empty = EMPTY.get_or_init(FactorizedSchema::new);
52 let left = child_schemas.first().unwrap_or(empty);
53 let right = child_schemas.get(1).unwrap_or(empty);
54 (left, right)
55}
56
57pub(crate) fn dispatch_module(node: &LogicalNodeEnum) -> &'static str {
65 match node {
66 LogicalNodeEnum::ScanVertices(_) => "access",
67 LogicalNodeEnum::ScanEdges(_) => "access",
68 LogicalNodeEnum::GetVertices(_) => "access",
69 LogicalNodeEnum::GetEdges(_) => "access",
70 LogicalNodeEnum::GetNeighbors(_) => "access",
71 LogicalNodeEnum::Start(_) => "access",
72 LogicalNodeEnum::Project(_) => "operation",
73 LogicalNodeEnum::Filter(_) => "operation",
74 LogicalNodeEnum::Aggregate(_) => "operation",
75 LogicalNodeEnum::Flatten(_) => "operation",
76 LogicalNodeEnum::Sort(_) => "operation",
77 LogicalNodeEnum::TopN(_) => "operation",
78 LogicalNodeEnum::Window(_) => "operation",
79 LogicalNodeEnum::Dedup(_) => "operation",
80 LogicalNodeEnum::Limit(_) => "operation",
81 LogicalNodeEnum::Skip(_) => "operation",
82 LogicalNodeEnum::Sample(_) => "operation",
83 LogicalNodeEnum::InnerJoin(_) => "join",
84 LogicalNodeEnum::LeftJoin(_) => "join",
85 LogicalNodeEnum::RightJoin(_) => "join",
86 LogicalNodeEnum::CrossJoin(_) => "join",
87 LogicalNodeEnum::FullOuterJoin(_) => "join",
88 LogicalNodeEnum::SemiJoin(_) => "join",
89 LogicalNodeEnum::Traverse(_) => "traversal",
90 LogicalNodeEnum::Expand(_) => "traversal",
91 LogicalNodeEnum::ExpandAll(_) => "traversal",
92 LogicalNodeEnum::AppendVertices(_) => "traversal",
93 LogicalNodeEnum::BiExpand(_) => "traversal",
94 LogicalNodeEnum::BiTraverse(_) => "traversal",
95 LogicalNodeEnum::Union(_) => "set_ops",
96 LogicalNodeEnum::Minus(_) => "set_ops",
97 LogicalNodeEnum::Intersect(_) => "set_ops",
98 LogicalNodeEnum::WcoIntersect(_) => "set_ops",
99 LogicalNodeEnum::Assign(_) => "assign",
100 LogicalNodeEnum::Select(_) => "control_flow",
101 LogicalNodeEnum::Loop(_) => "control_flow",
102 LogicalNodeEnum::BeginTransaction(_) => "control_flow",
103 LogicalNodeEnum::Commit(_) => "control_flow",
104 LogicalNodeEnum::Rollback(_) => "control_flow",
105 LogicalNodeEnum::PassThrough(_) => "control_flow",
106 LogicalNodeEnum::Argument(_) => "control_flow",
107 LogicalNodeEnum::Unwind(_) => "unwind",
108 LogicalNodeEnum::FulltextSearch(_) => "flat_leaf",
109 LogicalNodeEnum::FulltextLookup(_) => "flat_leaf",
110 LogicalNodeEnum::MatchFulltext(_) => "flat_leaf",
111 #[cfg(feature = "vector")]
112 LogicalNodeEnum::VectorSearch(_) => "flat_leaf",
113 #[cfg(feature = "vector")]
114 LogicalNodeEnum::VectorLookup(_) => "flat_leaf",
115 #[cfg(feature = "vector")]
116 LogicalNodeEnum::VectorMatch(_) => "flat_leaf",
117 LogicalNodeEnum::InsertVertices(_) => "flat_leaf",
118 LogicalNodeEnum::InsertEdges(_) => "flat_leaf",
119 LogicalNodeEnum::Update(_) => "flat_leaf",
120 LogicalNodeEnum::DeleteVertices(_) => "flat_leaf",
121 LogicalNodeEnum::DeleteEdges(_) => "flat_leaf",
122 LogicalNodeEnum::DeleteIndex(_) => "flat_leaf",
123 LogicalNodeEnum::PipeDeleteVertices(_) => "flat_leaf",
124 LogicalNodeEnum::PipeDeleteEdges(_) => "flat_leaf",
125 LogicalNodeEnum::CopyFrom(_) => "flat_leaf",
126 LogicalNodeEnum::CopyTo(_) => "flat_leaf",
127 LogicalNodeEnum::DataCollect(_) => "flat_leaf",
128 LogicalNodeEnum::Materialize(_) => "flat_leaf",
129 LogicalNodeEnum::RollUpApply(_) => "flat_leaf",
130 LogicalNodeEnum::Apply(_) => "flat_leaf",
131 LogicalNodeEnum::PatternApply(_) => "flat_leaf",
132 LogicalNodeEnum::CorrelatedApply(_) => "flat_leaf",
133 LogicalNodeEnum::MultiShortestPath(_) => "flat_leaf",
134 LogicalNodeEnum::BFSShortest(_) => "flat_leaf",
135 LogicalNodeEnum::AllPaths(_) => "flat_leaf",
136 LogicalNodeEnum::ShortestPath(_) => "flat_leaf",
137 }
138}
139
140pub(super) fn bi_expand_schema(
150 child_schemas: &[FactorizedSchema],
151 output_aliases: &[String],
152) -> Result<FactorizedSchema, FactorizationError> {
153 let mut schema = child_schemas.first().cloned().unwrap_or_default();
154 if schema.has_unflat_group() {
155 if let Some(pos) = schema.unflat_group_pos() {
156 schema.flatten_group(pos)?;
157 }
158 }
159 if let Some(build) = child_schemas.get(1) {
160 let mapping = schema.merge_groups_from(build);
161 for (expr_id, gpos) in build.expression_to_group_iter() {
162 let new_pos = mapping.get(gpos).copied().unwrap_or(*gpos);
163 schema.insert_to_scope_may_repeat(expr_id.clone(), new_pos)?;
164 }
165 if schema.has_unflat_group() {
166 if let Some(pos) = schema.unflat_group_pos() {
167 schema.flatten_group(pos)?;
168 }
169 }
170 }
171 let out = schema.create_group();
172 for alias in output_aliases {
173 schema.insert_name_for_group(alias.clone(), out)?;
174 }
175 schema.validate_at_most_one_unflat()?;
176 Ok(schema)
177}
178
179impl FactorizedSchemaCompute for LogicalNodeEnum {
180 fn compute_factorized_schema(
181 &mut self,
182 child_schemas: &[FactorizedSchema],
183 ) -> Result<FactorizedSchema, FactorizationError> {
184 debug_assert!(!dispatch_module(self).is_empty());
187 let schema = match self {
188 LogicalNodeEnum::ScanVertices(n) => access::scan_vertices(n)?,
189 LogicalNodeEnum::ScanEdges(n) => access::scan_edges(n)?,
190 LogicalNodeEnum::GetVertices(n) => access::get_vertices(n, child_schemas)?,
191 LogicalNodeEnum::GetEdges(n) => access::get_edges(n)?,
192 LogicalNodeEnum::GetNeighbors(n) => access::get_neighbors(n, child_schemas)?,
193 LogicalNodeEnum::Start(_) => access::start()?,
194
195 LogicalNodeEnum::Project(n) => operation::project(n, child_schemas)?,
196 LogicalNodeEnum::Filter(n) => operation::filter(n, child_schemas)?,
197 LogicalNodeEnum::Aggregate(n) => operation::aggregate(n, child_schemas)?,
198 LogicalNodeEnum::Flatten(n) => operation::flatten(n, child_schemas)?,
199 LogicalNodeEnum::Sort(n) => operation::sort(n, child_schemas)?,
200 LogicalNodeEnum::TopN(n) => operation::top_n(n, child_schemas)?,
201 LogicalNodeEnum::Window(n) => operation::window(n, child_schemas)?,
202 LogicalNodeEnum::Dedup(n) => operation::dedup(n, child_schemas)?,
203 LogicalNodeEnum::Limit(n) => operation::limit(n, child_schemas)?,
204 LogicalNodeEnum::Skip(n) => operation::skip(n, child_schemas)?,
205 LogicalNodeEnum::Sample(n) => operation::sample(n, child_schemas)?,
206
207 LogicalNodeEnum::InnerJoin(n) => {
208 let (left, right) = pair(child_schemas);
209 join::binary_join_inner(left, right, &n.hash_keys, &n.probe_keys)?
210 }
211 LogicalNodeEnum::LeftJoin(n) => {
212 let (left, right) = pair(child_schemas);
213 join::binary_join_inner(left, right, &n.hash_keys, &n.probe_keys)?
214 }
215 LogicalNodeEnum::RightJoin(n) => {
216 let (left, right) = pair(child_schemas);
217 join::binary_join_right(left, right, &n.hash_keys, &n.probe_keys)?
218 }
219 LogicalNodeEnum::CrossJoin(n) => {
220 let (left, right) = pair(child_schemas);
221 if n.hash_keys.is_empty() && n.probe_keys.is_empty() {
222 join::cross_join_no_keys(left, right)?
223 } else {
224 join::binary_join_inner(left, right, &n.hash_keys, &n.probe_keys)?
225 }
226 }
227 LogicalNodeEnum::FullOuterJoin(n) => {
228 let (left, right) = pair(child_schemas);
229 join::binary_join_full_outer(left, right, &n.hash_keys, &n.probe_keys)?
230 }
231 LogicalNodeEnum::SemiJoin(n) => {
232 let (left, right) = pair(child_schemas);
233 join::binary_join_inner(left, right, &n.hash_keys, &n.probe_keys)?
234 }
235
236 LogicalNodeEnum::Traverse(n) => traversal::traverse(n, child_schemas)?,
237 LogicalNodeEnum::Expand(n) => traversal::expand(n, child_schemas)?,
238 LogicalNodeEnum::ExpandAll(n) => traversal::expand_all(n, child_schemas)?,
239 LogicalNodeEnum::AppendVertices(n) => traversal::append_vertices(n, child_schemas)?,
240 LogicalNodeEnum::BiExpand(n) => traversal::bi_expand(n, child_schemas)?,
241 LogicalNodeEnum::BiTraverse(n) => traversal::bi_traverse(n, child_schemas)?,
242
243 LogicalNodeEnum::Union(_) | LogicalNodeEnum::Minus(_) => {
244 set_ops::union_minus(child_schemas)?
245 }
246 LogicalNodeEnum::Intersect(_) => set_ops::intersect(child_schemas)?,
247 LogicalNodeEnum::WcoIntersect(n) => set_ops::wco_intersect(n, child_schemas)?,
248
249 LogicalNodeEnum::Assign(n) => assign::assign(n, child_schemas)?,
250
251 LogicalNodeEnum::Select(n) => control_flow::select(n, child_schemas)?,
252 LogicalNodeEnum::Loop(n) => control_flow::loop_node(n, child_schemas)?,
253 LogicalNodeEnum::BeginTransaction(_)
254 | LogicalNodeEnum::Commit(_)
255 | LogicalNodeEnum::Rollback(_)
256 | LogicalNodeEnum::PassThrough(_)
257 | LogicalNodeEnum::Argument(_) => control_flow::passthrough(child_schemas)?,
258
259 LogicalNodeEnum::Unwind(n) => unwind::unwind(n, child_schemas)?,
260
261 LogicalNodeEnum::FulltextSearch(_)
262 | LogicalNodeEnum::FulltextLookup(_)
263 | LogicalNodeEnum::MatchFulltext(_) => flat_leaf::flat_leaf()?,
264 #[cfg(feature = "vector")]
265 LogicalNodeEnum::VectorSearch(_)
266 | LogicalNodeEnum::VectorLookup(_)
267 | LogicalNodeEnum::VectorMatch(_) => flat_leaf::flat_leaf()?,
268
269 LogicalNodeEnum::DataCollect(_)
273 | LogicalNodeEnum::Materialize(_)
274 | LogicalNodeEnum::RollUpApply(_) => flat_leaf::flatten_all_from_child(child_schemas)?,
275
276 LogicalNodeEnum::InsertVertices(_)
277 | LogicalNodeEnum::InsertEdges(_)
278 | LogicalNodeEnum::Update(_) => flat_leaf::flat_leaf()?,
279
280 LogicalNodeEnum::Apply(_)
284 | LogicalNodeEnum::PatternApply(_)
285 | LogicalNodeEnum::CorrelatedApply(_)
286 | LogicalNodeEnum::MultiShortestPath(_)
287 | LogicalNodeEnum::BFSShortest(_)
288 | LogicalNodeEnum::AllPaths(_)
289 | LogicalNodeEnum::ShortestPath(_) => flat_leaf::barrier_binary(child_schemas)?,
290
291 _ => flat_leaf::flatten_all_from_child(child_schemas)?,
292 };
293 debug_assert!(
294 schema.has_at_most_one_unflat(),
295 "compute_factorized_schema: at most one unflat group invariant violated"
296 );
297 Ok(schema)
298 }
299
300 fn compute_flat_schema(
301 &mut self,
302 child_schemas: &[FactorizedSchema],
303 ) -> Result<FactorizedSchema, FactorizationError> {
304 let flat_children: Vec<FactorizedSchema> = child_schemas
305 .iter()
306 .map(|cs| cs.flat_copy())
307 .collect::<Result<Vec<_>, _>>()?;
308 let mut result = self.compute_factorized_schema(&flat_children)?;
309 result.flatten_all()?;
310 result.validate_at_most_one_unflat()?;
311 Ok(result)
312 }
313}
314
315#[cfg(test)]
316mod tests;