1use super::*;
2
3impl NavigationExpr {
4 pub fn identity(&self) -> &NavigationPathIdentity {
5 &self.identity
6 }
7
8 pub fn root_relation(&self) -> &RootRelationInstance {
9 &self.identity.root
10 }
11
12 pub fn steps(&self) -> &[ReferenceStep] {
13 &self.steps
14 }
15
16 pub fn terminal_column(&self) -> &SchemaColumnId {
17 &self.identity.terminal_column
18 }
19
20 pub fn terminal_type(&self) -> DataType {
21 self.terminal_type
22 }
23
24 pub fn nullable(&self) -> bool {
25 self.nullable
26 }
27
28 pub fn display_path(&self) -> &str {
29 &self.display_path
30 }
31
32 pub fn validate(&self, engine: &dyn Engine) -> Result<()> {
33 engine.validate_schema_table_id(self.identity.root.table())?;
34 for step in &self.steps {
35 engine.validate_schema_table_id(step.source_column().table())?;
36 engine.validate_schema_table_id(step.target_table())?;
37 }
38 engine.validate_schema_table_id(self.terminal_column().table())
39 }
40}
41
42#[derive(Debug, Clone)]
43struct ScopeRoot {
44 ordinal: u32,
45 visible_name_lower: String,
46 table: Option<SchemaTableId>,
47}
48
49fn collect_source_visible_names(source: &Expression, output: &mut FxHashSet<String>) {
50 match source {
51 Expression::TableSource(table) => {
52 output.insert(
53 table
54 .alias
55 .as_ref()
56 .unwrap_or(&table.name)
57 .value_lower()
58 .to_string(),
59 );
60 }
61 Expression::JoinSource(join) => {
62 collect_source_visible_names(&join.left, output);
63 collect_source_visible_names(&join.right, output);
64 }
65 Expression::SubquerySource(subquery) => {
66 if let Some(alias) = &subquery.alias {
67 output.insert(alias.value_lower().to_string());
68 }
69 }
70 Expression::CteReference(cte) => {
71 output.insert(
72 cte.alias
73 .as_ref()
74 .unwrap_or(&cte.name)
75 .value_lower()
76 .to_string(),
77 );
78 }
79 Expression::ValuesSource(values) => {
80 if let Some(alias) = &values.alias {
81 output.insert(alias.value_lower().to_string());
82 }
83 }
84 Expression::FunctionTableSource(function) => {
85 if let Some(alias) = &function.alias {
86 output.insert(alias.value_lower().to_string());
87 }
88 }
89 _ => {}
90 }
91}
92
93struct NavigationBinder<'a> {
94 engine: &'a dyn Engine,
95 ctx: Option<&'a ExecutionContext>,
96 next_relation_ordinal: u32,
97}
98
99impl<'a> NavigationBinder<'a> {
100 fn new(engine: &'a dyn Engine) -> Self {
101 Self {
102 engine,
103 ctx: None,
104 next_relation_ordinal: 0,
105 }
106 }
107
108 fn with_context(engine: &'a dyn Engine, ctx: &'a ExecutionContext) -> Self {
109 Self {
110 engine,
111 ctx: Some(ctx),
112 next_relation_ordinal: 0,
113 }
114 }
115
116 fn bind_select(
117 &mut self,
118 select: &SelectStatement,
119 output: &mut Vec<NavigationExpr>,
120 ) -> Result<()> {
121 let visible_ctes: FxHashSet<&str> = select
122 .with
123 .as_ref()
124 .into_iter()
125 .flat_map(|with| with.ctes.iter())
126 .map(|cte| cte.name.value_lower())
127 .collect();
128
129 let mut roots = Vec::new();
130 if let Some(source) = select.table_expr.as_deref() {
131 self.collect_roots(source, &visible_ctes, &mut roots)?;
132 }
133
134 self.visit_select_expressions(select, &roots, output)
135 }
136
137 fn visit_select_expressions(
138 &mut self,
139 select: &SelectStatement,
140 roots: &[ScopeRoot],
141 output: &mut Vec<NavigationExpr>,
142 ) -> Result<()> {
143 if let Some(source) = select.table_expr.as_deref() {
144 self.visit_source_expressions(source, roots, output)?;
145 }
146 for expression in select.distinct_on.iter().chain(&select.columns) {
147 self.visit_expression(expression, roots, output)?;
148 }
149 if let Some(expression) = select.where_clause.as_deref() {
150 self.visit_expression(expression, roots, output)?;
151 }
152 for expression in &select.group_by.columns {
153 self.visit_expression(expression, roots, output)?;
154 }
155 if let GroupByModifier::GroupingSets(sets) = &select.group_by.modifier {
156 for set in sets {
157 for expression in set {
158 self.visit_expression(expression, roots, output)?;
159 }
160 }
161 }
162 if let Some(expression) = select.having.as_deref() {
163 self.visit_expression(expression, roots, output)?;
164 }
165 for window in &select.window_defs {
166 for expression in &window.partition_by {
167 self.visit_expression(expression, roots, output)?;
168 }
169 for order in &window.order_by {
170 self.visit_expression(&order.expression, roots, output)?;
171 }
172 if let Some(frame) = &window.frame {
173 self.visit_frame(frame, roots, output)?;
174 }
175 }
176 for order in &select.order_by {
177 self.visit_expression(&order.expression, roots, output)?;
178 }
179 if let Some(expression) = select.limit.as_deref() {
180 self.visit_expression(expression, roots, output)?;
181 }
182 if let Some(expression) = select.offset.as_deref() {
183 self.visit_expression(expression, roots, output)?;
184 }
185 Ok(())
186 }
187
188 fn collect_roots(
189 &mut self,
190 source: &Expression,
191 visible_ctes: &FxHashSet<&str>,
192 roots: &mut Vec<ScopeRoot>,
193 ) -> Result<()> {
194 match source {
195 Expression::TableSource(table) => {
196 let visible_name = table.alias.as_ref().unwrap_or(&table.name);
197 let table_id = if visible_ctes.contains(table.name.value_lower())
198 || self
199 .ctx
200 .is_some_and(|ctx| ctx.get_cte_by_lower(table.name.value_lower()).is_some())
201 || !self.engine.table_exists(table.name.value())?
202 {
203 None
204 } else {
205 Some(self.engine.bind_schema_table_id(table.name.value())?)
206 };
207 self.push_root(visible_name.value_lower(), table_id, roots)?;
208 }
209 Expression::JoinSource(join) => {
210 self.collect_roots(&join.left, visible_ctes, roots)?;
211 self.collect_roots(&join.right, visible_ctes, roots)?;
212 }
213 Expression::SubquerySource(subquery) => {
214 if let Some(alias) = &subquery.alias {
215 self.push_root(alias.value_lower(), None, roots)?;
216 }
217 }
218 Expression::CteReference(cte) => {
219 let visible_name = cte.alias.as_ref().unwrap_or(&cte.name);
220 self.push_root(visible_name.value_lower(), None, roots)?;
221 }
222 Expression::ValuesSource(values) => {
223 if let Some(alias) = &values.alias {
224 self.push_root(alias.value_lower(), None, roots)?;
225 }
226 }
227 Expression::FunctionTableSource(function) => {
228 if let Some(alias) = &function.alias {
229 self.push_root(alias.value_lower(), None, roots)?;
230 }
231 }
232 _ => {}
233 }
234 Ok(())
235 }
236
237 fn push_root(
238 &mut self,
239 visible_name_lower: &str,
240 table: Option<SchemaTableId>,
241 roots: &mut Vec<ScopeRoot>,
242 ) -> Result<()> {
243 let ordinal = self.next_relation_ordinal;
244 self.next_relation_ordinal =
245 self.next_relation_ordinal.checked_add(1).ok_or_else(|| {
246 Error::navigation(
247 NavigationErrorCode::UnsupportedReferenceShape,
248 "query contains too many relation instances",
249 )
250 })?;
251 roots.push(ScopeRoot {
252 ordinal,
253 visible_name_lower: visible_name_lower.to_string(),
254 table,
255 });
256 Ok(())
257 }
258
259 fn visit_source_expressions(
260 &mut self,
261 source: &Expression,
262 roots: &[ScopeRoot],
263 output: &mut Vec<NavigationExpr>,
264 ) -> Result<()> {
265 match source {
266 Expression::TableSource(table) => {
267 if let Some(as_of) = &table.as_of {
268 self.visit_expression(&as_of.value, roots, output)?;
269 }
270 }
271 Expression::JoinSource(join) => {
272 self.visit_source_expressions(&join.left, roots, output)?;
273 self.visit_source_expressions(&join.right, roots, output)?;
274 if let Some(condition) = &join.condition {
275 self.visit_expression(condition, roots, output)?;
276 }
277 }
278 Expression::ValuesSource(values) => {
279 for row in &values.rows {
280 for expression in row {
281 self.visit_expression(expression, roots, output)?;
282 }
283 }
284 }
285 Expression::FunctionTableSource(function) => {
286 for expression in &function.arguments {
287 self.visit_expression(expression, roots, output)?;
288 }
289 }
290 Expression::SubquerySource(_) | Expression::CteReference(_) => {}
291 _ => {}
292 }
293 Ok(())
294 }
295
296 fn visit_expression(
297 &mut self,
298 expression: &Expression,
299 roots: &[ScopeRoot],
300 output: &mut Vec<NavigationExpr>,
301 ) -> Result<()> {
302 match expression {
303 Expression::QualifiedIdentifier(path) => {
304 if let Some(bound) = self.bind_path(path, roots)? {
305 output.push(bound);
306 }
307 }
308 Expression::QualifiedStar(star) => {
309 self.bind_navigation_wildcard(&star.qualifier, roots)?
310 }
311 Expression::Prefix(value) => self.visit_expression(&value.right, roots, output)?,
312 Expression::Infix(value) => {
313 self.visit_expression(&value.left, roots, output)?;
314 self.visit_expression(&value.right, roots, output)?;
315 }
316 Expression::List(value) => {
317 for expression in &value.elements {
318 self.visit_expression(expression, roots, output)?;
319 }
320 }
321 Expression::Distinct(value) => self.visit_expression(&value.expr, roots, output)?,
322 Expression::Exists(value) => {
323 self.visit_correlated_navigation(&value.subquery, roots, output)?
324 }
325 Expression::AllAny(value) => {
326 self.visit_expression(&value.left, roots, output)?;
327 self.visit_correlated_navigation(&value.subquery, roots, output)?;
328 }
329 Expression::In(value) => {
330 self.visit_expression(&value.left, roots, output)?;
331 self.visit_expression(&value.right, roots, output)?;
332 }
333 Expression::InHashSet(value) => self.visit_expression(&value.column, roots, output)?,
334 Expression::Between(value) => {
335 self.visit_expression(&value.expr, roots, output)?;
336 self.visit_expression(&value.lower, roots, output)?;
337 self.visit_expression(&value.upper, roots, output)?;
338 }
339 Expression::Like(value) => {
340 self.visit_expression(&value.left, roots, output)?;
341 self.visit_expression(&value.pattern, roots, output)?;
342 if let Some(escape) = &value.escape {
343 self.visit_expression(escape, roots, output)?;
344 }
345 }
346 Expression::ScalarSubquery(value) => {
347 self.visit_correlated_navigation(&value.subquery, roots, output)?
348 }
349 Expression::ExpressionList(value) => {
350 for expression in &value.expressions {
351 self.visit_expression(expression, roots, output)?;
352 }
353 }
354 Expression::Case(value) => {
355 if let Some(expression) = &value.value {
356 self.visit_expression(expression, roots, output)?;
357 }
358 for clause in &value.when_clauses {
359 self.visit_expression(&clause.condition, roots, output)?;
360 self.visit_expression(&clause.then_result, roots, output)?;
361 }
362 if let Some(expression) = &value.else_value {
363 self.visit_expression(expression, roots, output)?;
364 }
365 }
366 Expression::Cast(value) => self.visit_expression(&value.expr, roots, output)?,
367 Expression::FunctionCall(value) => {
368 for expression in &value.arguments {
369 self.visit_expression(expression, roots, output)?;
370 }
371 for order in &value.order_by {
372 self.visit_expression(&order.expression, roots, output)?;
373 }
374 if let Some(filter) = &value.filter {
375 self.visit_expression(filter, roots, output)?;
376 }
377 }
378 Expression::Aliased(value) => {
379 self.visit_expression(&value.expression, roots, output)?;
380 }
381 Expression::Window(value) => {
382 for expression in &value.function.arguments {
383 self.visit_expression(expression, roots, output)?;
384 }
385 for order in &value.function.order_by {
386 self.visit_expression(&order.expression, roots, output)?;
387 }
388 if let Some(filter) = &value.function.filter {
389 self.visit_expression(filter, roots, output)?;
390 }
391 for expression in &value.partition_by {
392 self.visit_expression(expression, roots, output)?;
393 }
394 for order in &value.order_by {
395 self.visit_expression(&order.expression, roots, output)?;
396 }
397 if let Some(frame) = &value.frame {
398 self.visit_frame(frame, roots, output)?;
399 }
400 }
401 Expression::SubquerySource(_) => {}
402 Expression::TableSource(_)
403 | Expression::JoinSource(_)
404 | Expression::ValuesSource(_)
405 | Expression::FunctionTableSource(_)
406 | Expression::Identifier(_)
407 | Expression::IntegerLiteral(_)
408 | Expression::FloatLiteral(_)
409 | Expression::StringLiteral(_)
410 | Expression::BooleanLiteral(_)
411 | Expression::NullLiteral(_)
412 | Expression::IntervalLiteral(_)
413 | Expression::BoundValue(_)
414 | Expression::Parameter(_)
415 | Expression::CteReference(_)
416 | Expression::Star(_)
417 | Expression::Default(_) => {}
418 }
419 Ok(())
420 }
421
422 fn visit_correlated_navigation(
423 &mut self,
424 select: &SelectStatement,
425 outer_roots: &[ScopeRoot],
426 output: &mut Vec<NavigationExpr>,
427 ) -> Result<()> {
428 let mut shadowed = FxHashSet::default();
429 if let Some(source) = select.table_expr.as_deref() {
430 collect_source_visible_names(source, &mut shadowed);
431 }
432 if let Some(with) = &select.with {
433 shadowed.extend(
434 with.ctes
435 .iter()
436 .map(|cte| cte.name.value_lower().to_string()),
437 );
438 }
439 let visible_outer = outer_roots
440 .iter()
441 .filter(|root| !shadowed.contains(&root.visible_name_lower))
442 .cloned()
443 .collect::<Vec<_>>();
444 if visible_outer.is_empty() {
445 return Ok(());
446 }
447 if select.table_expr.is_none() {
448 return self.visit_select_expressions(select, &visible_outer, output);
449 }
450
451 let names = visible_outer
452 .iter()
453 .map(|root| root.visible_name_lower.as_str())
454 .collect::<FxHashSet<_>>();
455 let mut candidates = Vec::new();
456 radixdb_sql::ast::walk_select_tree(select, &mut |expression| {
457 let Expression::QualifiedIdentifier(path) = expression else {
458 return;
459 };
460 if path.component_count() > 2 && names.contains(path.qualifier.value_lower()) {
461 candidates.push(path.clone());
462 }
463 });
464 for path in candidates {
465 if let Some(bound) = self.bind_path(&path, &visible_outer)? {
466 output.push(bound);
467 }
468 }
469 Ok(())
470 }
471
472 fn visit_frame(
473 &mut self,
474 frame: &WindowFrame,
475 roots: &[ScopeRoot],
476 output: &mut Vec<NavigationExpr>,
477 ) -> Result<()> {
478 self.visit_frame_bound(&frame.start, roots, output)?;
479 if let Some(end) = &frame.end {
480 self.visit_frame_bound(end, roots, output)?;
481 }
482 Ok(())
483 }
484
485 fn visit_frame_bound(
486 &mut self,
487 bound: &WindowFrameBound,
488 roots: &[ScopeRoot],
489 output: &mut Vec<NavigationExpr>,
490 ) -> Result<()> {
491 if let WindowFrameBound::Preceding(expression) | WindowFrameBound::Following(expression) =
492 bound
493 {
494 self.visit_expression(expression, roots, output)?;
495 }
496 Ok(())
497 }
498
499 fn bind_path(
500 &self,
501 path: &QualifiedIdentifier,
502 roots: &[ScopeRoot],
503 ) -> Result<Option<NavigationExpr>> {
504 let components: Vec<&str> = path.components().map(|item| item.value()).collect();
505 debug_assert!(components.len() >= 2);
506 let root_name_lower = components[0].to_lowercase();
507 let explicit_roots: Vec<&ScopeRoot> = roots
508 .iter()
509 .filter(|root| root.visible_name_lower == root_name_lower)
510 .collect();
511
512 if !explicit_roots.is_empty() {
513 if components.len() == 2 {
516 return Ok(None);
517 }
518 if explicit_roots.len() != 1 {
519 return Err(Error::navigation(
520 NavigationErrorCode::AmbiguousRoot,
521 format!("relation root '{}' is ambiguous", components[0]),
522 ));
523 }
524 let root = explicit_roots[0];
525 if root.table.is_none() {
526 return Err(Error::navigation(
527 NavigationErrorCode::UnsupportedReferenceShape,
528 format!(
529 "navigation root '{}' is not a physical catalog table",
530 components[0]
531 ),
532 ));
533 }
534 return self
535 .bind_from_root(
536 root,
537 &components[1..components.len() - 1],
538 components.last().unwrap(),
539 path,
540 )
541 .map(Some);
542 }
543
544 let source_component = components[0];
545 let mut candidates = Vec::new();
546 let mut non_reference_roots = 0usize;
547 for root in roots {
548 let Some(table) = &root.table else {
549 continue;
550 };
551 let schema = self.engine.get_table_schema(table.table_name())?;
552 if !schema.has_column(source_component) {
553 continue;
554 }
555 let source = self.engine.bind_schema_column_id(table, source_component)?;
556 if self.engine.get_reference_descriptor(&source)?.is_some() {
557 candidates.push(root);
558 } else {
559 non_reference_roots += 1;
560 }
561 }
562
563 if candidates.len() > 1 {
564 return Err(Error::navigation(
565 NavigationErrorCode::AmbiguousRoot,
566 format!(
567 "reference shorthand '{}' matches {} relation instances; qualify the root",
568 source_component,
569 candidates.len()
570 ),
571 ));
572 }
573 if let Some(root) = candidates.first() {
574 return self
575 .bind_from_root(
576 root,
577 &components[..components.len() - 1],
578 components.last().unwrap(),
579 path,
580 )
581 .map(Some);
582 }
583 if non_reference_roots > 0 {
584 return Err(Error::navigation(
585 NavigationErrorCode::NotAReference,
586 format!("column '{}' is not a navigable reference", source_component),
587 ));
588 }
589 if components.len() == 2 {
593 return Ok(None);
594 }
595 Err(Error::navigation(
596 NavigationErrorCode::UnknownRoot,
597 format!(
598 "'{}' is neither a visible relation root nor an unambiguous reference column",
599 source_component
600 ),
601 ))
602 }
603
604 fn bind_from_root(
605 &self,
606 root: &ScopeRoot,
607 step_names: &[&str],
608 terminal_name: &str,
609 path: &QualifiedIdentifier,
610 ) -> Result<NavigationExpr> {
611 if step_names.len() > MAX_NAVIGATION_STEPS {
612 return Err(Error::navigation(
613 NavigationErrorCode::UnsupportedReferenceShape,
614 format!(
615 "navigation path '{}' has {} steps; maximum is {MAX_NAVIGATION_STEPS}",
616 path,
617 step_names.len()
618 ),
619 ));
620 }
621 let root_table = root.table.as_ref().expect("physical root checked");
622 let mut current_table = root_table.clone();
623 let mut steps = Vec::with_capacity(step_names.len());
624 let mut nullable = false;
625
626 for step_name in step_names {
627 let source = self
628 .engine
629 .bind_schema_column_id(¤t_table, step_name)
630 .map_err(|error| match error {
631 Error::ColumnNotFound(_) => Error::navigation(
632 NavigationErrorCode::NotAReference,
633 format!(
634 "'{}.{}' is not a reference column",
635 current_table.table_name(),
636 step_name
637 ),
638 ),
639 other => other,
640 })?;
641 let descriptor = self
642 .engine
643 .get_reference_descriptor(&source)?
644 .ok_or_else(|| {
645 Error::navigation(
646 NavigationErrorCode::NotAReference,
647 format!(
648 "'{}.{}' is not a reference column",
649 current_table.table_name(),
650 step_name
651 ),
652 )
653 })?;
654 nullable |= descriptor.source_nullable();
655 let step = ReferenceStep {
656 identity: ReferenceStepIdentity {
657 source_column: descriptor.source().clone(),
658 target_key_column: descriptor.target().clone(),
659 },
660 target_key: descriptor.target_key(),
661 source_nullable: descriptor.source_nullable(),
662 };
663 current_table = descriptor.target().table().clone();
664 steps.push(step);
665 }
666
667 let terminal_column = self
668 .engine
669 .bind_schema_column_id(¤t_table, terminal_name)
670 .map_err(|error| match error {
671 Error::ColumnNotFound(_) => Error::navigation(
672 NavigationErrorCode::TargetColumnNotFound,
673 format!(
674 "target column '{}.{}' does not exist",
675 current_table.table_name(),
676 terminal_name
677 ),
678 ),
679 other => other,
680 })?;
681 let terminal_schema = self.engine.get_table_schema(current_table.table_name())?;
682 let terminal = terminal_schema
683 .get_column(terminal_column.ordinal())
684 .ok_or_else(|| {
685 Error::navigation(
686 NavigationErrorCode::SchemaChanged,
687 format!(
688 "terminal column ordinal {} no longer exists in '{}'",
689 terminal_column.ordinal(),
690 current_table.table_name()
691 ),
692 )
693 })?;
694 nullable |= terminal.nullable;
695
696 if self.engine.schema_epoch() != root_table.schema_generation() {
697 return Err(Error::navigation(
698 NavigationErrorCode::SchemaChanged,
699 format!("schema generation changed while binding path '{}'", path),
700 ));
701 }
702
703 let root_relation = RootRelationInstance {
704 ordinal: root.ordinal,
705 table: root_table.clone(),
706 };
707 let identity = NavigationPathIdentity {
708 root: root_relation,
709 steps: steps.iter().map(|step| step.identity.clone()).collect(),
710 terminal_column,
711 };
712 Ok(NavigationExpr {
713 identity,
714 steps,
715 terminal_type: terminal.data_type,
716 nullable,
717 display_path: path.to_string(),
718 })
719 }
720
721 fn bind_navigation_wildcard(&self, qualifier: &str, roots: &[ScopeRoot]) -> Result<()> {
722 let qualifier_lower = qualifier.to_lowercase();
723 if roots
724 .iter()
725 .any(|root| root.visible_name_lower == qualifier_lower)
726 {
727 return Ok(());
728 }
729
730 let mut matches = 0usize;
731 for root in roots {
732 let Some(table) = &root.table else {
733 continue;
734 };
735 let schema = self.engine.get_table_schema(table.table_name())?;
736 if !schema.has_column(qualifier) {
737 continue;
738 }
739 let source = self.engine.bind_schema_column_id(table, qualifier)?;
740 if self.engine.get_reference_descriptor(&source)?.is_some() {
741 matches += 1;
742 }
743 }
744 match matches {
745 0 => Ok(()),
746 1 => Err(Error::navigation(
747 NavigationErrorCode::UnsupportedReferenceShape,
748 format!("navigation wildcard '{}.*' is not allowed", qualifier),
749 )),
750 _ => Err(Error::navigation(
751 NavigationErrorCode::AmbiguousRoot,
752 format!("navigation wildcard root '{}' is ambiguous", qualifier),
753 )),
754 }
755 }
756}
757
758pub fn bind_navigation_paths(
761 engine: &dyn Engine,
762 select: &SelectStatement,
763) -> Result<Vec<NavigationExpr>> {
764 let generation = engine.schema_epoch();
765 let mut output = Vec::new();
766 NavigationBinder::new(engine).bind_select(select, &mut output)?;
767 if engine.schema_epoch() != generation {
768 return Err(Error::navigation(
769 NavigationErrorCode::SchemaChanged,
770 "schema generation changed while binding statement",
771 ));
772 }
773 Ok(output)
774}
775
776pub fn bind_reference_expand_plan(
777 engine: &dyn Engine,
778 select: &SelectStatement,
779) -> Result<Option<ReferenceExpandPlan>> {
780 let paths = bind_navigation_paths(engine, select)?;
781 if paths.is_empty() {
782 Ok(None)
783 } else {
784 ReferenceExpandPlan::build(paths).map(Some)
785 }
786}
787
788pub fn bind_reference_expand_plan_for_execution(
789 engine: &dyn Engine,
790 select: &SelectStatement,
791 ctx: &ExecutionContext,
792) -> Result<Option<ReferenceExpandPlan>> {
793 let generation = engine.schema_epoch();
794 let mut paths = Vec::new();
795 NavigationBinder::with_context(engine, ctx).bind_select(select, &mut paths)?;
796 if engine.schema_epoch() != generation {
797 return Err(Error::navigation(
798 NavigationErrorCode::SchemaChanged,
799 "schema generation changed while binding statement",
800 ));
801 }
802 if paths.is_empty() {
803 Ok(None)
804 } else {
805 ReferenceExpandPlan::build(paths).map(Some)
806 }
807}
808
809pub fn reject_navigation_in_write_statement(
812 engine: &dyn Engine,
813 statement: &Statement,
814) -> Result<()> {
815 let mut paths = Vec::new();
816 match statement {
817 Statement::Insert(insert) => {
818 let roots = dml_target_roots(engine, &insert.table_name, None)?;
819 let mut binder = NavigationBinder::new(engine);
820 for row in &insert.values {
821 for expression in row {
822 bind_dml_expression(engine, &mut binder, &roots, expression, &mut paths)?;
823 }
824 }
825 if let Some(select) = insert.select.as_deref() {
826 bind_dml_select_tree(engine, select, &mut paths)?;
827 }
828 for expression in insert.update_expressions.iter().chain(&insert.returning) {
829 bind_dml_expression(engine, &mut binder, &roots, expression, &mut paths)?;
830 }
831 }
832 Statement::Update(update) => {
833 let roots = dml_target_roots(engine, &update.table_name, None)?;
834 let mut binder = NavigationBinder::new(engine);
835 for expression in update.updates.values() {
836 bind_dml_expression(engine, &mut binder, &roots, expression, &mut paths)?;
837 }
838 if let Some(expression) = update.where_clause.as_deref() {
839 bind_dml_expression(engine, &mut binder, &roots, expression, &mut paths)?;
840 }
841 for expression in &update.returning {
842 bind_dml_expression(engine, &mut binder, &roots, expression, &mut paths)?;
843 }
844 }
845 Statement::Delete(delete) => {
846 let roots = dml_target_roots(engine, &delete.table_name, delete.alias.as_ref())?;
847 let mut binder = NavigationBinder::new(engine);
848 if let Some(expression) = delete.where_clause.as_deref() {
849 bind_dml_expression(engine, &mut binder, &roots, expression, &mut paths)?;
850 }
851 for expression in &delete.returning {
852 bind_dml_expression(engine, &mut binder, &roots, expression, &mut paths)?;
853 }
854 }
855 Statement::CreateTable(create) => {
856 if let Some(select) = create.as_select.as_deref() {
857 bind_dml_select_tree(engine, select, &mut paths)?;
858 }
859 }
860 Statement::CreateView(create) => {
861 bind_dml_select_tree(engine, &create.query, &mut paths)?;
862 }
863 Statement::Explain(explain) => {
864 return reject_navigation_in_write_statement(engine, &explain.statement)
865 }
866 _ => return Ok(()),
867 }
868
869 if let Some(path) = paths.first() {
870 if matches!(statement, Statement::CreateView(_)) {
871 return Err(Error::navigation(
872 NavigationErrorCode::UnsupportedReferenceShape,
873 format!(
874 "navigation path '{}' cannot be persisted in a VIEW definition in the first version",
875 path.display_path()
876 ),
877 ));
878 }
879 return Err(Error::navigation(
880 NavigationErrorCode::ReadOnly,
881 format!(
882 "navigation path '{}' cannot appear in a write statement; name the target table and relationship explicitly",
883 path.display_path()
884 ),
885 ));
886 }
887 Ok(())
888}
889
890fn dml_target_roots(
891 engine: &dyn Engine,
892 table_name: &Identifier,
893 alias: Option<&Identifier>,
894) -> Result<Vec<ScopeRoot>> {
895 let table = if engine.table_exists(table_name.value())? {
896 Some(engine.bind_schema_table_id(table_name.value())?)
897 } else {
898 None
899 };
900 Ok(vec![ScopeRoot {
901 ordinal: 0,
902 visible_name_lower: alias.unwrap_or(table_name).value_lower().to_string(),
903 table,
904 }])
905}
906
907fn bind_dml_expression(
908 engine: &dyn Engine,
909 binder: &mut NavigationBinder<'_>,
910 roots: &[ScopeRoot],
911 expression: &Expression,
912 output: &mut Vec<NavigationExpr>,
913) -> Result<()> {
914 binder.visit_expression(expression, roots, output)?;
915 if output.is_empty() {
916 bind_nested_dml_selects(engine, expression, output)?;
917 }
918 Ok(())
919}
920
921fn bind_nested_dml_selects(
922 engine: &dyn Engine,
923 expression: &Expression,
924 output: &mut Vec<NavigationExpr>,
925) -> Result<()> {
926 let mut error = None;
927 radixdb_sql::ast::walk_expression_tree(expression, &mut |node| {
928 if error.is_some() || !output.is_empty() {
929 return;
930 }
931 let nested = match node {
932 Expression::Exists(value) => Some(value.subquery.as_ref()),
933 Expression::AllAny(value) => Some(value.subquery.as_ref()),
934 Expression::ScalarSubquery(value) => Some(value.subquery.as_ref()),
935 Expression::SubquerySource(value) => Some(value.subquery.as_ref()),
936 _ => None,
937 };
938 if let Some(select) = nested {
939 if let Err(current) = bind_dml_select_tree(engine, select, output) {
940 error = Some(current);
941 }
942 }
943 });
944 match error {
945 Some(error) => Err(error),
946 None => Ok(()),
947 }
948}
949
950fn bind_dml_select_tree(
951 engine: &dyn Engine,
952 select: &SelectStatement,
953 output: &mut Vec<NavigationExpr>,
954) -> Result<()> {
955 NavigationBinder::new(engine).bind_select(select, output)?;
956 if !output.is_empty() {
957 return Ok(());
958 }
959 if let Some(with) = &select.with {
960 for cte in &with.ctes {
961 bind_dml_select_tree(engine, &cte.query, output)?;
962 if !output.is_empty() {
963 return Ok(());
964 }
965 }
966 }
967 for operation in &select.set_operations {
968 bind_dml_select_tree(engine, &operation.right, output)?;
969 if !output.is_empty() {
970 return Ok(());
971 }
972 }
973
974 let mut error = None;
975 radixdb_sql::ast::walk_select_tree(select, &mut |node| {
976 if error.is_some() || !output.is_empty() {
977 return;
978 }
979 let nested = match node {
980 Expression::Exists(value) => Some(value.subquery.as_ref()),
981 Expression::AllAny(value) => Some(value.subquery.as_ref()),
982 Expression::ScalarSubquery(value) => Some(value.subquery.as_ref()),
983 Expression::SubquerySource(value) => Some(value.subquery.as_ref()),
984 _ => None,
985 };
986 if let Some(nested) = nested {
987 if let Err(current) = bind_dml_select_tree(engine, nested, output) {
988 error = Some(current);
989 }
990 }
991 });
992 match error {
993 Some(error) => Err(error),
994 None => Ok(()),
995 }
996}