nodedb_sql/planner/subquery/
in_list.rs1use sqlparser::ast::{self, Expr, SetExpr};
6
7use crate::error::{Result, SqlError};
8use crate::functions::registry::FunctionRegistry;
9use crate::parser::normalize::{SCHEMA_QUALIFIED_MSG, normalize_ident};
10use crate::types::*;
11
12use super::extract::SubqueryJoin;
13
14pub(super) fn try_plan_in_subquery(
16 outer_expr: &Expr,
17 subquery: &ast::Query,
18 negated: bool,
19 outer: &crate::resolver::columns::TableScope,
20 catalog: &dyn SqlCatalog,
21 functions: &FunctionRegistry,
22 temporal: crate::TemporalScope,
23) -> Result<Option<SubqueryJoin>> {
24 let outer_col = match outer_expr {
27 Expr::Identifier(ident) => {
28 let col = normalize_ident(ident);
29 outer.check_name(None, &col)?;
30 col
31 }
32 Expr::CompoundIdentifier(parts) if parts.len() >= 3 => {
33 let qualified: String = parts
34 .iter()
35 .map(normalize_ident)
36 .collect::<Vec<_>>()
37 .join(".");
38 return Err(SqlError::Unsupported {
39 detail: format!(
40 "schema-qualified column reference '{qualified}': {SCHEMA_QUALIFIED_MSG}"
41 ),
42 });
43 }
44 Expr::CompoundIdentifier(parts) if parts.len() == 2 => {
45 let qualifier = normalize_ident(&parts[0]);
46 let col = normalize_ident(&parts[1]);
47 outer.check_name(Some(&qualifier), &col)?;
48 col
49 }
50 _ => return Ok(None), };
52
53 let inner_plan = crate::planner::select::plan_query(subquery, catalog, functions, temporal)?;
55
56 let inner_col = extract_single_projected_column(subquery)?;
58
59 Ok(Some(SubqueryJoin {
60 on: vec![(outer_col, inner_col)],
61 inner_plan,
62 join_type: if negated {
63 JoinType::Anti
64 } else {
65 JoinType::Semi
66 },
67 }))
68}
69
70fn extract_single_projected_column(query: &ast::Query) -> Result<String> {
74 let select = match &*query.body {
75 SetExpr::Select(s) => s,
76 _ => {
77 return Err(SqlError::Unsupported {
78 detail: "subquery must be a simple SELECT".into(),
79 });
80 }
81 };
82
83 if select.projection.len() != 1 {
84 return Err(SqlError::Unsupported {
85 detail: format!(
86 "subquery must select exactly 1 column, got {}",
87 select.projection.len()
88 ),
89 });
90 }
91
92 match &select.projection[0] {
93 ast::SelectItem::UnnamedExpr(expr) => match expr {
94 Expr::Identifier(ident) => Ok(normalize_ident(ident)),
95 Expr::CompoundIdentifier(parts) if parts.len() >= 3 => {
96 let qualified: String = parts
97 .iter()
98 .map(normalize_ident)
99 .collect::<Vec<_>>()
100 .join(".");
101 Err(SqlError::Unsupported {
102 detail: format!(
103 "schema-qualified column reference '{qualified}': {SCHEMA_QUALIFIED_MSG}"
104 ),
105 })
106 }
107 Expr::CompoundIdentifier(parts) if parts.len() == 2 => Ok(normalize_ident(&parts[1])),
108 _ => Err(SqlError::Unsupported {
109 detail: "subquery projection must be a column reference".into(),
110 }),
111 },
112 ast::SelectItem::ExprWithAlias { alias, .. } => Ok(normalize_ident(alias)),
113 _ => Err(SqlError::Unsupported {
114 detail: "subquery projection must be a column reference".into(),
115 }),
116 }
117}