nodedb_sql/planner/
ast_helpers.rs1use sqlparser::ast;
6
7use crate::error::{Result, SqlError};
8use crate::parser::normalize::{normalize_ident, normalize_object_name_checked};
9use crate::planner::select::convert_where_to_filters;
10use crate::resolver::columns::ResolvedTable;
11use crate::types::Filter;
12
13pub fn qualified_ident_pair(expr: &ast::Expr) -> Option<(String, String)> {
15 match expr {
16 ast::Expr::CompoundIdentifier(parts) if parts.len() == 2 => {
17 Some((normalize_ident(&parts[0]), normalize_ident(&parts[1])))
18 }
19 _ => None,
20 }
21}
22
23pub fn flatten_and_expr(expr: &ast::Expr, out: &mut Vec<ast::Expr>) {
25 match expr {
26 ast::Expr::BinaryOp {
27 left,
28 op: ast::BinaryOperator::And,
29 right,
30 } => {
31 flatten_and_expr(left, out);
32 flatten_and_expr(right, out);
33 }
34 other => out.push(other.clone()),
35 }
36}
37
38pub fn rebuild_and_expr(mut conjuncts: Vec<ast::Expr>) -> ast::Expr {
40 let last = conjuncts.pop().expect("non-empty conjuncts");
41 conjuncts
42 .into_iter()
43 .rfold(last, |acc, next| ast::Expr::BinaryOp {
44 left: Box::new(next),
45 op: ast::BinaryOperator::And,
46 right: Box::new(acc),
47 })
48}
49
50pub fn strip_table_qualifier(expr: &ast::Expr, qualifier: &str) -> ast::Expr {
55 match expr {
56 ast::Expr::CompoundIdentifier(parts) if parts.len() == 2 => {
57 if normalize_ident(&parts[0]) == qualifier {
58 ast::Expr::Identifier(parts[1].clone())
59 } else {
60 expr.clone()
61 }
62 }
63 ast::Expr::BinaryOp { left, op, right } => ast::Expr::BinaryOp {
64 left: Box::new(strip_table_qualifier(left, qualifier)),
65 op: op.clone(),
66 right: Box::new(strip_table_qualifier(right, qualifier)),
67 },
68 ast::Expr::UnaryOp { op, expr: inner } => ast::Expr::UnaryOp {
69 op: *op,
70 expr: Box::new(strip_table_qualifier(inner, qualifier)),
71 },
72 ast::Expr::Nested(inner) => {
73 ast::Expr::Nested(Box::new(strip_table_qualifier(inner, qualifier)))
74 }
75 ast::Expr::IsNull(inner) => {
76 ast::Expr::IsNull(Box::new(strip_table_qualifier(inner, qualifier)))
77 }
78 ast::Expr::IsNotNull(inner) => {
79 ast::Expr::IsNotNull(Box::new(strip_table_qualifier(inner, qualifier)))
80 }
81 other => other.clone(),
82 }
83}
84
85pub fn find_foreign_qualifier(expr: &ast::Expr, valid_qualifiers: &[&str]) -> Option<String> {
92 match expr {
93 ast::Expr::CompoundIdentifier(parts) if parts.len() == 2 => {
94 let qualifier = normalize_ident(&parts[0]);
95 if valid_qualifiers.contains(&qualifier.as_str()) {
96 None
97 } else {
98 Some(qualifier)
99 }
100 }
101 ast::Expr::BinaryOp { left, right, .. } => find_foreign_qualifier(left, valid_qualifiers)
102 .or_else(|| find_foreign_qualifier(right, valid_qualifiers)),
103 ast::Expr::UnaryOp { expr: inner, .. }
104 | ast::Expr::Nested(inner)
105 | ast::Expr::IsNull(inner)
106 | ast::Expr::IsNotNull(inner) => find_foreign_qualifier(inner, valid_qualifiers),
107 _ => None,
108 }
109}
110
111fn reject_foreign_qualifier(expr: &ast::Expr, valid_qualifiers: &[&str]) -> Result<()> {
114 match find_foreign_qualifier(expr, valid_qualifiers) {
115 Some(bad) => Err(SqlError::UnknownTable { name: bad }),
116 None => Ok(()),
117 }
118}
119
120fn reject_qualifier(qualifier: &str, valid_qualifiers: &[&str]) -> Result<()> {
122 if valid_qualifiers.contains(&qualifier) {
123 Ok(())
124 } else {
125 Err(SqlError::UnknownTable {
126 name: qualifier.to_string(),
127 })
128 }
129}
130
131fn strip_table_qualifiers(expr: &ast::Expr, qualifiers: &[&str]) -> ast::Expr {
135 qualifiers
136 .iter()
137 .fold(expr.clone(), |acc, q| strip_table_qualifier(&acc, q))
138}
139
140pub fn strip_single_table_qualifiers(
149 select: &ast::Select,
150 valid_qualifiers: &[&str],
151) -> Result<ast::Select> {
152 let mut out = select.clone();
153
154 for item in &mut out.projection {
155 match item {
156 ast::SelectItem::UnnamedExpr(expr)
157 | ast::SelectItem::ExprWithAlias { expr, .. }
158 | ast::SelectItem::ExprWithAliases { expr, .. } => {
159 reject_foreign_qualifier(expr, valid_qualifiers)?;
160 *expr = strip_table_qualifiers(expr, valid_qualifiers);
161 }
162 ast::SelectItem::QualifiedWildcard(kind, opts) => {
163 if let ast::SelectItemQualifiedWildcardKind::ObjectName(name) = kind {
164 let qualifier = normalize_object_name_checked(name)?;
165 reject_qualifier(&qualifier, valid_qualifiers)?;
166 let opts = opts.clone();
168 *item = ast::SelectItem::Wildcard(opts);
169 }
170 }
171 ast::SelectItem::Wildcard(_) => {}
172 }
173 }
174
175 if let Some(expr) = &mut out.selection {
176 reject_foreign_qualifier(expr, valid_qualifiers)?;
177 *expr = strip_table_qualifiers(expr, valid_qualifiers);
178 }
179
180 if let ast::GroupByExpr::Expressions(exprs, _) = &mut out.group_by {
181 for expr in exprs {
182 reject_foreign_qualifier(expr, valid_qualifiers)?;
183 *expr = strip_table_qualifiers(expr, valid_qualifiers);
184 }
185 }
186
187 Ok(out)
188}
189
190pub(crate) fn single_table_qualifiers(table: &ResolvedTable) -> Vec<&str> {
193 let ref_name = table.ref_name();
194 if ref_name == table.name {
195 vec![table.name.as_str()]
196 } else {
197 vec![table.name.as_str(), ref_name]
198 }
199}
200
201pub fn strip_and_convert_filters(
204 conjuncts: Vec<ast::Expr>,
205 qualifier: &str,
206 scope: &crate::resolver::columns::TableScope,
207) -> Result<Vec<Filter>> {
208 if conjuncts.is_empty() {
209 return Ok(Vec::new());
210 }
211 let stripped: Vec<ast::Expr> = conjuncts
212 .into_iter()
213 .map(|c| strip_table_qualifier(&c, qualifier))
214 .collect();
215 let rebuilt = rebuild_and_expr(stripped);
216 convert_where_to_filters(&rebuilt, scope)
217}