nodedb_sql/planner/join/
constraint.rs1use sqlparser::ast;
6
7use crate::error::{Result, SqlError};
8use crate::parser::normalize::normalize_ident;
9use crate::resolver::expr::convert_expr;
10use crate::types::*;
11
12pub(super) type JoinSpec = (JoinType, Vec<(String, String)>, Option<SqlExpr>);
14
15pub(super) fn extract_join_spec(op: &ast::JoinOperator) -> Result<JoinSpec> {
17 match op {
18 ast::JoinOperator::Inner(constraint) | ast::JoinOperator::Join(constraint) => {
19 let (keys, cond) = extract_join_constraint(constraint)?;
20 Ok((JoinType::Inner, keys, cond))
21 }
22 ast::JoinOperator::Left(constraint) | ast::JoinOperator::LeftOuter(constraint) => {
23 let (keys, cond) = extract_join_constraint(constraint)?;
24 Ok((JoinType::Left, keys, cond))
25 }
26 ast::JoinOperator::Right(constraint) | ast::JoinOperator::RightOuter(constraint) => {
27 let (keys, cond) = extract_join_constraint(constraint)?;
28 Ok((JoinType::Right, keys, cond))
29 }
30 ast::JoinOperator::FullOuter(constraint) => {
31 let (keys, cond) = extract_join_constraint(constraint)?;
32 Ok((JoinType::Full, keys, cond))
33 }
34 ast::JoinOperator::CrossJoin(constraint) => {
35 let (keys, cond) = extract_join_constraint(constraint)?;
36 Ok((JoinType::Cross, keys, cond))
37 }
38 ast::JoinOperator::Semi(constraint) | ast::JoinOperator::LeftSemi(constraint) => {
39 let (keys, cond) = extract_join_constraint(constraint)?;
40 Ok((JoinType::Semi, keys, cond))
41 }
42 ast::JoinOperator::Anti(constraint) | ast::JoinOperator::LeftAnti(constraint) => {
43 let (keys, cond) = extract_join_constraint(constraint)?;
44 Ok((JoinType::Anti, keys, cond))
45 }
46 _ => Err(SqlError::Unsupported {
47 detail: format!("join type: {op:?}"),
48 }),
49 }
50}
51
52type JoinConstraintResult = (Vec<(String, String)>, Option<SqlExpr>);
54
55fn extract_join_constraint(constraint: &ast::JoinConstraint) -> Result<JoinConstraintResult> {
56 match constraint {
57 ast::JoinConstraint::On(expr) => {
58 let mut keys = Vec::new();
59 let mut non_equi = Vec::new();
60 extract_equi_keys(expr, &mut keys, &mut non_equi)?;
61 let cond = if non_equi.is_empty() {
62 None
63 } else {
64 let mut combined = convert_expr(&non_equi[0])?;
65 for pred in &non_equi[1..] {
66 combined = SqlExpr::BinaryOp {
67 left: Box::new(combined),
68 op: crate::types::BinaryOp::And,
69 right: Box::new(convert_expr(pred)?),
70 };
71 }
72 Some(combined)
73 };
74 Ok((keys, cond))
75 }
76 ast::JoinConstraint::Using(columns) => {
77 let keys = columns
78 .iter()
79 .map(|c| {
80 let name = crate::parser::normalize::normalize_object_name_checked(c)?;
81 Ok((name.clone(), name))
82 })
83 .collect::<Result<Vec<_>>>()?;
84 Ok((keys, None))
85 }
86 ast::JoinConstraint::Natural => Err(SqlError::Unsupported {
87 detail: "NATURAL JOIN is not supported; use explicit ON or USING clause".into(),
88 }),
89 ast::JoinConstraint::None => Err(SqlError::Unsupported {
90 detail: "implicit cross join (no ON/USING clause) is not supported".into(),
91 }),
92 }
93}
94
95fn extract_equi_keys(
96 expr: &ast::Expr,
97 keys: &mut Vec<(String, String)>,
98 non_equi: &mut Vec<ast::Expr>,
99) -> Result<()> {
100 match expr {
101 ast::Expr::BinaryOp {
102 left,
103 op: ast::BinaryOperator::And,
104 right,
105 } => {
106 extract_equi_keys(left, keys, non_equi)?;
107 extract_equi_keys(right, keys, non_equi)?;
108 }
109 ast::Expr::BinaryOp {
110 left,
111 op: ast::BinaryOperator::Eq,
112 right,
113 } => {
114 if let (Some(l), Some(r)) = (extract_col_ref(left), extract_col_ref(right)) {
115 keys.push((l, r));
116 } else {
117 non_equi.push(expr.clone());
118 }
119 }
120 _ => {
121 non_equi.push(expr.clone());
122 }
123 }
124 Ok(())
125}
126
127fn extract_col_ref(expr: &ast::Expr) -> Option<String> {
128 match expr {
129 ast::Expr::Identifier(ident) => Some(normalize_ident(ident)),
130 ast::Expr::CompoundIdentifier(parts) if parts.len() == 2 => Some(format!(
132 "{}.{}",
133 normalize_ident(&parts[0]),
134 normalize_ident(&parts[1])
135 )),
136 ast::Expr::CompoundIdentifier(_) => None,
140 _ => None,
141 }
142}