1use sqlparser::ast::{self, Select};
7
8use super::array_arm;
9use super::constraint::extract_join_spec;
10use crate::error::{Result, SqlError};
11use crate::functions::registry::FunctionRegistry;
12use crate::planner::lateral::plan::{
13 is_lateral_derived, lateral_alias_from_factor, plan_lateral_join, subquery_from_factor,
14};
15use crate::resolver::columns::TableScope;
16use crate::types::*;
17
18pub fn plan_join_from_select(
19 select: &Select,
20 scope: &TableScope,
21 catalog: &dyn SqlCatalog,
22 functions: &FunctionRegistry,
23 temporal: crate::TemporalScope,
24) -> Result<Option<SqlPlan>> {
25 let from = &select.from[0];
26
27 let left_plan =
29 if let Some(plan) = array_arm::try_plan_relation(&from.relation, catalog, temporal)? {
30 plan
31 } else {
32 scan_for_relation(&from.relation, scope)?
33 };
34
35 let outer_alias = scan_alias_from_relation(&from.relation);
36
37 let mut current_plan = left_plan;
38
39 for join_item in &from.joins {
40 if is_lateral_derived(&join_item.relation) {
42 let lateral_alias =
43 lateral_alias_from_factor(&join_item.relation).ok_or_else(|| {
44 SqlError::Unsupported {
45 detail: "LATERAL subquery requires an alias (e.g. LATERAL (...) AS x)"
46 .into(),
47 }
48 })?;
49 let subquery = subquery_from_factor(&join_item.relation)
50 .expect("is_lateral_derived guarantees Derived variant");
51 let left_join = is_left_join_operator(&join_item.join_operator);
52 let projection = super::super::select::convert_projection(&select.projection)?;
53 return Ok(Some(plan_lateral_join(
54 current_plan,
55 outer_alias,
56 subquery,
57 &lateral_alias,
58 left_join,
59 projection,
60 catalog,
61 functions,
62 temporal,
63 )?));
64 }
65
66 let right_plan = if let Some(plan) =
68 array_arm::try_plan_relation(&join_item.relation, catalog, temporal)?
69 {
70 plan
71 } else {
72 scan_for_relation(&join_item.relation, scope)?
73 };
74
75 let (join_type, on_keys, condition) = extract_join_spec(&join_item.join_operator)?;
76
77 current_plan = SqlPlan::Join {
78 left: Box::new(current_plan),
79 right: Box::new(right_plan),
80 on: on_keys,
81 join_type,
82 condition,
83 limit: 10000,
84 projection: Vec::new(),
85 filters: Vec::new(),
86 };
87 }
88
89 let (subquery_joins, effective_where) = if let Some(expr) = &select.selection {
90 let extraction =
91 super::super::subquery::extract_subqueries(expr, catalog, functions, temporal)?;
92 (extraction.joins, extraction.remaining_where)
93 } else {
94 (Vec::new(), None)
95 };
96
97 let projection = super::super::select::convert_projection(&select.projection)?;
98 let filters = match &effective_where {
99 Some(expr) => super::super::select::convert_where_to_filters(expr)?,
100 None => Vec::new(),
101 };
102
103 for sq in subquery_joins {
104 current_plan = SqlPlan::Join {
105 left: Box::new(current_plan),
106 right: Box::new(sq.inner_plan),
107 on: vec![(sq.outer_column, sq.inner_column)],
108 join_type: sq.join_type,
109 condition: None,
110 limit: 10000,
111 projection: Vec::new(),
112 filters: Vec::new(),
113 };
114 }
115
116 let group_by_non_empty = match &select.group_by {
117 ast::GroupByExpr::All(_) => true,
118 ast::GroupByExpr::Expressions(exprs, _) => !exprs.is_empty(),
119 };
120 if super::super::select::convert_projection(&select.projection).is_ok() && group_by_non_empty {
121 let aggregates = super::super::aggregate::extract_aggregates_from_projection(
122 &select.projection,
123 functions,
124 )?;
125 let group_by = super::super::aggregate::convert_group_by(&select.group_by)?;
126 let having = match &select.having {
127 Some(expr) => super::super::select::convert_where_to_filters(expr)?,
128 None => Vec::new(),
129 };
130 return Ok(Some(SqlPlan::Aggregate {
131 input: Box::new(current_plan),
132 group_by,
133 aggregates,
134 having,
135 limit: 10000,
136 grouping_sets: None,
137 sort_keys: Vec::new(),
138 }));
139 }
140
141 if let SqlPlan::Join {
142 projection: ref mut proj,
143 filters: ref mut filt,
144 ..
145 } = current_plan
146 {
147 *proj = projection;
148 *filt = filters;
149 }
150 Ok(Some(current_plan))
151}
152
153fn scan_alias_from_relation(factor: &ast::TableFactor) -> Option<String> {
155 match factor {
156 ast::TableFactor::Table { name, alias, .. } => alias
157 .as_ref()
158 .map(|a| crate::parser::normalize::normalize_ident(&a.name))
159 .or_else(|| crate::parser::normalize::normalize_object_name_checked(name).ok()),
160 _ => None,
161 }
162}
163
164fn is_left_join_operator(op: &ast::JoinOperator) -> bool {
166 matches!(
167 op,
168 ast::JoinOperator::Left(_) | ast::JoinOperator::LeftOuter(_)
169 )
170}
171
172fn scan_for_relation(rel: &ast::TableFactor, scope: &TableScope) -> Result<SqlPlan> {
174 let (rel_name, rel_alias) =
175 crate::parser::normalize::table_name_from_factor(rel)?.ok_or_else(|| {
176 SqlError::Unsupported {
177 detail: "non-table JOIN target".into(),
178 }
179 })?;
180 let table = scope
181 .tables
182 .values()
183 .find(|t| t.name == rel_name || t.alias.as_deref() == Some(&rel_name))
184 .ok_or_else(|| SqlError::UnknownTable {
185 name: rel_name.clone(),
186 })?;
187 Ok(SqlPlan::Scan {
188 collection: table.name.clone(),
189 alias: rel_alias.or_else(|| table.alias.clone()),
190 engine: table.info.engine,
191 filters: Vec::new(),
192 projection: Vec::new(),
193 sort_keys: Vec::new(),
194 limit: None,
195 offset: 0,
196 distinct: false,
197 window_functions: Vec::new(),
198 temporal: crate::temporal::TemporalScope::default(),
199 })
200}