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