1use sqlparser::ast::{self, Select};
7
8use super::array_arm;
9use super::constraint::extract_join_spec;
10use super::derived_side::plan_derived_side;
11use crate::error::{Result, SqlError};
12use crate::functions::registry::FunctionRegistry;
13use crate::planner::lateral::plan::{LateralJoinArgs, plan_lateral_join};
14use crate::planner::lateral::subquery::{
15 is_lateral_derived, lateral_alias_from_factor, subquery_from_factor,
16};
17use crate::resolver::columns::{ColumnNaming, TableScope};
18use crate::types::*;
19
20pub fn plan_join_from_select(
21 select: &Select,
22 scope: &TableScope,
23 catalog: &dyn SqlCatalog,
24 functions: &FunctionRegistry,
25 temporal: crate::TemporalScope,
26) -> Result<Option<SqlPlan>> {
27 let from = &select.from[0];
28
29 let has_lateral = from
34 .joins
35 .iter()
36 .any(|join_item| is_lateral_derived(&join_item.relation));
37 let join_row_scope;
38 let scope = if has_lateral {
39 scope
40 } else {
41 join_row_scope = scope.clone().with_column_naming(ColumnNaming::JoinRow);
42 &join_row_scope
43 };
44
45 let left_plan =
47 if let Some(plan) = array_arm::try_plan_relation(&from.relation, catalog, temporal)? {
48 plan
49 } else if let Some(plan) =
50 plan_derived_side(&from.relation, scope, catalog, functions, temporal)?
51 {
52 plan
53 } else {
54 scan_for_relation(&from.relation, scope, temporal)?
55 };
56
57 let outer_alias = scan_alias_from_relation(&from.relation)?;
58
59 let mut current_plan = left_plan;
60
61 for join_item in &from.joins {
62 if is_lateral_derived(&join_item.relation) {
64 let lateral_alias =
65 lateral_alias_from_factor(&join_item.relation)?.ok_or_else(|| {
66 SqlError::Unsupported {
67 detail: "LATERAL subquery requires an alias (e.g. LATERAL (...) AS x)"
68 .into(),
69 }
70 })?;
71 let subquery = subquery_from_factor(&join_item.relation)
72 .expect("is_lateral_derived guarantees Derived variant");
73 let left_join = is_left_join_operator(&join_item.join_operator);
74 let projection = super::super::select::convert_projection(&select.projection, scope)?;
75 return Ok(Some(plan_lateral_join(LateralJoinArgs {
76 outer_plan: current_plan,
77 outer_alias,
78 subquery,
79 lateral_alias: &lateral_alias,
80 left_join,
81 outer_projection: projection,
82 outer_scope: scope,
83 catalog,
84 functions,
85 temporal,
86 })?));
87 }
88
89 let right_plan = if let Some(plan) =
91 array_arm::try_plan_relation(&join_item.relation, catalog, temporal)?
92 {
93 plan
94 } else if let Some(plan) =
95 plan_derived_side(&join_item.relation, scope, catalog, functions, temporal)?
96 {
97 plan
98 } else {
99 scan_for_relation(&join_item.relation, scope, temporal)?
100 };
101
102 let right_ids = right_side_identifiers(&join_item.relation)?;
103 let (join_type, mut on_keys, condition) = extract_join_spec(
104 &join_item.join_operator,
105 scope,
106 right_ids.first().map(String::as_str),
107 )?;
108
109 super::constraint::orient_keys_to_sides(&mut on_keys, &right_ids);
115
116 current_plan = SqlPlan::Join {
117 left: Box::new(current_plan),
118 right: Box::new(right_plan),
119 on: on_keys,
120 join_type,
121 condition,
122 limit: None,
123 projection: Vec::new(),
124 filters: Vec::new(),
125 };
126 }
127
128 let (subquery_joins, effective_where) = if let Some(expr) = &select.selection {
129 let extraction =
130 super::super::subquery::extract_subqueries(expr, scope, catalog, functions, temporal)?;
131 (extraction.joins, extraction.remaining_where)
132 } else {
133 (Vec::new(), None)
134 };
135
136 let projection = super::super::select::convert_projection(&select.projection, scope)?;
137 let filters = match &effective_where {
138 Some(expr) => super::super::select::convert_where_to_filters(expr, scope)?,
139 None => Vec::new(),
140 };
141
142 for sq in subquery_joins {
143 current_plan = SqlPlan::Join {
144 left: Box::new(current_plan),
145 right: Box::new(sq.inner_plan),
146 on: sq.on,
147 join_type: sq.join_type,
148 condition: None,
149 limit: None,
150 projection: Vec::new(),
151 filters: Vec::new(),
152 };
153 }
154
155 if super::super::select::has_aggregation(select, functions) {
156 if let SqlPlan::Join {
158 filters: ref mut filt,
159 ..
160 } = current_plan
161 {
162 *filt = filters;
163 }
164 let clauses = super::super::aggregate_clauses::plan_aggregate_clauses(
167 select,
168 None,
169 &[],
170 scope,
171 functions,
172 )?;
173 return Ok(Some(SqlPlan::Aggregate {
174 input: Box::new(current_plan),
175 group_by: clauses.group_by,
176 group_by_aliases: clauses.group_by_aliases,
177 output_order: clauses.output_order,
178 aggregates: clauses.aggregates,
179 having: clauses.having,
180 limit: super::super::aggregate::AGGREGATE_NO_LIMIT,
181 grouping_sets: clauses.grouping_sets,
182 sort_keys: Vec::new(),
183 }));
184 }
185
186 if let SqlPlan::Join {
187 projection: ref mut proj,
188 filters: ref mut filt,
189 ..
190 } = current_plan
191 {
192 *proj = projection;
193 *filt = filters;
194 }
195 Ok(Some(current_plan))
196}
197
198fn right_side_identifiers(factor: &ast::TableFactor) -> Result<Vec<String>> {
202 let mut ids = Vec::new();
203 if let Some((name, alias)) = crate::parser::normalize::table_name_from_factor(factor)? {
204 if let Some(alias) = alias {
205 ids.push(alias);
206 }
207 ids.push(name);
208 } else if let Some(alias) = derived_alias(factor)? {
209 ids.push(alias);
210 }
211 Ok(ids)
212}
213
214fn scan_alias_from_relation(factor: &ast::TableFactor) -> Result<Option<String>> {
217 match crate::parser::normalize::table_name_from_factor(factor)? {
218 Some((name, alias)) => Ok(Some(alias.unwrap_or(name))),
219 None => derived_alias(factor),
220 }
221}
222
223fn derived_alias(factor: &ast::TableFactor) -> Result<Option<String>> {
225 match factor {
226 ast::TableFactor::Derived {
227 alias: Some(alias), ..
228 } => crate::reserved::check_ast_identifier(&alias.name).map(Some),
229 _ => Ok(None),
230 }
231}
232
233fn is_left_join_operator(op: &ast::JoinOperator) -> bool {
235 matches!(
236 op,
237 ast::JoinOperator::Left(_) | ast::JoinOperator::LeftOuter(_)
238 )
239}
240
241fn scan_for_relation(
246 rel: &ast::TableFactor,
247 scope: &TableScope,
248 temporal: crate::TemporalScope,
249) -> Result<SqlPlan> {
250 let (rel_name, rel_alias) =
251 crate::parser::normalize::table_name_from_factor(rel)?.ok_or_else(|| {
252 SqlError::Unsupported {
253 detail: "non-table JOIN target".into(),
254 }
255 })?;
256 let table = scope
257 .tables
258 .values()
259 .find(|t| t.name == rel_name || t.alias.as_deref() == Some(&rel_name))
260 .ok_or_else(|| SqlError::UnknownTable {
261 name: rel_name.clone(),
262 })?;
263 temporal.require_bitemporal(&table.name, table.info.bitemporal)?;
264 Ok(SqlPlan::Scan {
265 collection: table.name.clone(),
266 alias: rel_alias.or_else(|| table.alias.clone()),
267 engine: table.info.engine,
268 filters: Vec::new(),
269 projection: Vec::new(),
270 sort_keys: Vec::new(),
271 limit: None,
272 offset: 0,
273 distinct: false,
274 window_functions: Vec::new(),
275 temporal,
276 })
277}