1use sqlparser::ast;
6
7use crate::error::{Result, SqlError};
8use crate::parser::normalize::normalize_ident;
9use crate::planner::predicate_coerce::coerce_predicate_literals;
10use crate::resolver::ColumnScope;
11use crate::resolver::columns::{BareColumn, ColumnNaming, TableScope};
12use crate::resolver::expr::convert_expr;
13use crate::types::*;
14
15pub(super) type JoinSpec = (JoinType, Vec<(String, String)>, Option<SqlExpr>);
17
18pub(super) fn extract_join_spec(
23 op: &ast::JoinOperator,
24 scope: &TableScope,
25 right_ref: Option<&str>,
26) -> Result<JoinSpec> {
27 let (join_type, constraint) = match op {
28 ast::JoinOperator::Inner(constraint) | ast::JoinOperator::Join(constraint) => {
29 (JoinType::Inner, constraint)
30 }
31 ast::JoinOperator::Left(constraint) | ast::JoinOperator::LeftOuter(constraint) => {
32 (JoinType::Left, constraint)
33 }
34 ast::JoinOperator::Right(constraint) | ast::JoinOperator::RightOuter(constraint) => {
35 (JoinType::Right, constraint)
36 }
37 ast::JoinOperator::FullOuter(constraint) => (JoinType::Full, constraint),
38 ast::JoinOperator::CrossJoin(ast::JoinConstraint::None) => {
41 return Ok((JoinType::Cross, Vec::new(), None));
42 }
43 ast::JoinOperator::CrossJoin(constraint) => (JoinType::Cross, constraint),
44 ast::JoinOperator::Semi(constraint) | ast::JoinOperator::LeftSemi(constraint) => {
45 (JoinType::Semi, constraint)
46 }
47 ast::JoinOperator::Anti(constraint) | ast::JoinOperator::LeftAnti(constraint) => {
48 (JoinType::Anti, constraint)
49 }
50 _ => {
51 return Err(SqlError::Unsupported {
52 detail: format!("join type: {op:?}"),
53 });
54 }
55 };
56 let (keys, cond) = extract_join_constraint(constraint, scope, right_ref)?;
57 Ok((join_type, keys, cond))
58}
59
60type JoinConstraintResult = (Vec<(String, String)>, Option<SqlExpr>);
62
63fn extract_join_constraint(
64 constraint: &ast::JoinConstraint,
65 scope: &TableScope,
66 right_ref: Option<&str>,
67) -> Result<JoinConstraintResult> {
68 match constraint {
69 ast::JoinConstraint::On(expr) => {
70 let mut keys = Vec::new();
71 let mut non_equi = Vec::new();
72 extract_equi_keys(expr, &mut keys, &mut non_equi, scope)?;
73 let cond = if non_equi.is_empty() {
74 None
75 } else {
76 let mut combined = convert_expr(&non_equi[0], &ColumnScope::Relations(scope))?;
77 for pred in &non_equi[1..] {
78 combined = SqlExpr::BinaryOp {
79 left: Box::new(combined),
80 op: crate::types::BinaryOp::And,
81 right: Box::new(convert_expr(pred, &ColumnScope::Relations(scope))?),
82 };
83 }
84 coerce_predicate_literals(&mut combined, scope)?;
88 Some(combined)
89 };
90 Ok((keys, cond))
91 }
92 ast::JoinConstraint::Using(columns) => {
95 let keys = columns
96 .iter()
97 .map(|c| {
98 let name = crate::parser::normalize::normalize_object_name_checked(c)?;
99 scope.check_using_column(right_ref, &name)?;
100 Ok((name.clone(), name))
101 })
102 .collect::<Result<Vec<_>>>()?;
103 Ok((keys, None))
104 }
105 ast::JoinConstraint::Natural => Err(SqlError::Unsupported {
106 detail: "NATURAL JOIN is not supported; use explicit ON or USING clause".into(),
107 }),
108 ast::JoinConstraint::None => Err(SqlError::Unsupported {
109 detail: "implicit cross join (no ON/USING clause) is not supported".into(),
110 }),
111 }
112}
113
114fn extract_equi_keys(
115 expr: &ast::Expr,
116 keys: &mut Vec<(String, String)>,
117 non_equi: &mut Vec<ast::Expr>,
118 scope: &TableScope,
119) -> Result<()> {
120 match expr {
121 ast::Expr::BinaryOp {
122 left,
123 op: ast::BinaryOperator::And,
124 right,
125 } => {
126 extract_equi_keys(left, keys, non_equi, scope)?;
127 extract_equi_keys(right, keys, non_equi, scope)?;
128 }
129 ast::Expr::BinaryOp {
130 left,
131 op: ast::BinaryOperator::Eq,
132 right,
133 } => {
134 if let (Some(l), Some(r)) = (extract_col_ref(left), extract_col_ref(right)) {
135 keys.push((resolve_join_key(scope, l)?, resolve_join_key(scope, r)?));
136 } else {
137 non_equi.push(expr.clone());
138 }
139 }
140 _ => {
141 non_equi.push(expr.clone());
142 }
143 }
144 Ok(())
145}
146
147pub(super) fn orient_keys_to_sides(keys: &mut [(String, String)], right_ids: &[String]) {
162 let qualifier = |k: &str| k.rsplit_once('.').map(|(q, _)| q.to_string());
163 let on_right = |k: &str| {
164 qualifier(k)
165 .map(|q| right_ids.iter().any(|id| id == &q))
166 .unwrap_or(false)
167 };
168 for (l, r) in keys.iter_mut() {
169 if on_right(l) && !on_right(r) {
172 std::mem::swap(l, r);
173 }
174 }
175}
176
177fn resolve_join_key(scope: &TableScope, key: String) -> Result<String> {
184 if let Some((table, column)) = key.rsplit_once('.') {
185 scope.check_name(Some(table), column)?;
186 return Ok(key);
187 }
188 let owner = scope.resolve_bare(&key)?;
189 Ok(match (scope.column_naming(), owner) {
190 (ColumnNaming::JoinRow, BareColumn::Relation(table)) => {
191 format!("{}.{key}", table.ref_name())
192 }
193 (ColumnNaming::JoinRow, BareColumn::Output | BareColumn::Outer)
194 | (ColumnNaming::AsWritten | ColumnNaming::OutputRow, _) => key,
195 })
196}
197
198fn extract_col_ref(expr: &ast::Expr) -> Option<String> {
199 match expr {
200 ast::Expr::Identifier(ident) => Some(normalize_ident(ident)),
201 ast::Expr::CompoundIdentifier(parts) if parts.len() == 2 => Some(format!(
203 "{}.{}",
204 normalize_ident(&parts[0]),
205 normalize_ident(&parts[1])
206 )),
207 ast::Expr::CompoundIdentifier(_) => None,
211 _ => None,
212 }
213}