nodedb_sql/planner/lateral/
subquery.rs1use sqlparser::ast;
7
8use crate::error::{Result, SqlError};
9use crate::parser::normalize::normalize_ident;
10use crate::reserved::check_ast_identifier;
11use crate::resolver::columns::TableScope;
12use crate::types::Filter;
13
14pub(super) fn extract_inner_alias(select: &sqlparser::ast::Select) -> Result<Option<String>> {
16 let Some(from) = select.from.first() else {
17 return Ok(None);
18 };
19 match &from.relation {
20 ast::TableFactor::Table { alias, .. } => alias
21 .as_ref()
22 .map(|alias| check_ast_identifier(&alias.name))
23 .transpose(),
24 _ => Ok(None),
25 }
26}
27
28pub(super) fn extract_inner_collection(select: &sqlparser::ast::Select) -> Result<String> {
30 let from = select.from.first().ok_or_else(|| SqlError::Unsupported {
31 detail: "LATERAL subquery must have a FROM clause".into(),
32 })?;
33 crate::parser::normalize::table_name_from_factor(&from.relation)?
34 .map(|(name, _)| name)
35 .ok_or_else(|| SqlError::Unsupported {
36 detail: "LATERAL LateralTopK subquery must reference a plain table".into(),
37 })
38}
39
40pub(super) fn inner_non_correlated_filters(
42 select: &sqlparser::ast::Select,
43 outer_alias: &str,
44 scope: &TableScope,
45) -> Result<Vec<Filter>> {
46 let Some(where_expr) = &select.selection else {
47 return Ok(Vec::new());
48 };
49 let remaining = strip_outer_refs(where_expr, outer_alias);
50 match remaining {
51 Some(expr) => crate::planner::select::convert_where_to_filters(&expr, scope),
52 None => Ok(Vec::new()),
53 }
54}
55
56fn strip_outer_refs(expr: &ast::Expr, outer_alias: &str) -> Option<ast::Expr> {
58 match expr {
59 ast::Expr::BinaryOp {
60 left,
61 op: ast::BinaryOperator::And,
62 right,
63 } => {
64 let l = strip_outer_refs(left, outer_alias);
65 let r = strip_outer_refs(right, outer_alias);
66 match (l, r) {
67 (None, None) => None,
68 (Some(e), None) | (None, Some(e)) => Some(e),
69 (Some(l), Some(r)) => Some(ast::Expr::BinaryOp {
70 left: Box::new(l),
71 op: ast::BinaryOperator::And,
72 right: Box::new(r),
73 }),
74 }
75 }
76 ast::Expr::BinaryOp { left, right, .. } => {
77 if refs_outer(left, outer_alias) || refs_outer(right, outer_alias) {
78 None
79 } else {
80 Some(expr.clone())
81 }
82 }
83 ast::Expr::Nested(inner) => strip_outer_refs(inner, outer_alias),
84 _ => Some(expr.clone()),
85 }
86}
87
88fn refs_outer(expr: &ast::Expr, outer_alias: &str) -> bool {
89 match expr {
90 ast::Expr::CompoundIdentifier(parts) if parts.len() == 2 => {
91 normalize_ident(&parts[0]).eq_ignore_ascii_case(outer_alias)
92 }
93 ast::Expr::BinaryOp { left, right, .. } => {
94 refs_outer(left, outer_alias) || refs_outer(right, outer_alias)
95 }
96 _ => false,
97 }
98}
99
100pub(super) fn limit_from_query(query: &ast::Query) -> Result<Option<usize>> {
104 match &query.limit_clause {
105 Some(ast::LimitClause::LimitOffset {
106 limit: Some(limit), ..
107 })
108 | Some(ast::LimitClause::OffsetCommaLimit { limit, .. }) => {
109 Ok(crate::coerce::checked_row_bound("LIMIT", limit)?.limit())
110 }
111 Some(ast::LimitClause::LimitOffset { limit: None, .. }) | None => Ok(None),
112 }
113}
114
115pub(super) fn reject_lateral_offset(query: &ast::Query) -> Result<()> {
125 let offset_expr = match &query.limit_clause {
126 Some(ast::LimitClause::LimitOffset {
127 offset: Some(offset),
128 ..
129 }) => Some(&offset.value),
130 Some(ast::LimitClause::OffsetCommaLimit { offset, .. }) => Some(offset),
131 Some(ast::LimitClause::LimitOffset { offset: None, .. }) | None => None,
132 };
133 let Some(expr) = offset_expr else {
134 return Ok(());
135 };
136 if crate::coerce::checked_row_bound("OFFSET", expr)?.offset() > 0 {
137 return Err(SqlError::Unsupported {
138 detail: "OFFSET inside a LATERAL subquery is not supported".into(),
139 });
140 }
141 Ok(())
142}
143
144pub fn lateral_alias_from_factor(factor: &ast::TableFactor) -> Result<Option<String>> {
146 match factor {
147 ast::TableFactor::Derived { alias, .. } => alias
148 .as_ref()
149 .map(|alias| check_ast_identifier(&alias.name))
150 .transpose(),
151 _ => Ok(None),
152 }
153}
154
155pub fn is_lateral_derived(factor: &ast::TableFactor) -> bool {
157 matches!(factor, ast::TableFactor::Derived { lateral: true, .. })
158}
159
160pub fn subquery_from_factor(factor: &ast::TableFactor) -> Option<&ast::Query> {
162 match factor {
163 ast::TableFactor::Derived { subquery, .. } => Some(subquery),
164 _ => None,
165 }
166}