nodedb_sql/planner/select/
helpers.rs1use sqlparser::ast;
7
8use crate::error::{Result, SqlError};
9use crate::functions::registry::FunctionRegistry;
10use crate::parser::normalize::{SCHEMA_QUALIFIED_MSG, normalize_ident};
11use crate::resolver::expr::convert_expr;
12use crate::types::*;
13
14pub(super) fn source_projection(plan: &SqlPlan) -> Vec<Projection> {
19 match plan {
20 SqlPlan::Scan { projection, .. }
21 | SqlPlan::Join { projection, .. }
22 | SqlPlan::TextSearch { projection, .. } => projection.clone(),
23 _ => Vec::new(),
24 }
25}
26
27pub fn convert_projection(items: &[ast::SelectItem]) -> Result<Vec<Projection>> {
29 let mut result = Vec::new();
30 for item in items {
31 match item {
32 ast::SelectItem::UnnamedExpr(expr) => {
33 let sql_expr = convert_expr(expr)?;
34 match &sql_expr {
35 SqlExpr::Column { table, name } => {
36 result.push(Projection::Column(qualified_name(table.as_deref(), name)));
37 }
38 SqlExpr::Wildcard => {
39 result.push(Projection::Star);
40 }
41 _ => {
42 result.push(Projection::Computed {
43 expr: sql_expr,
44 alias: format!("{expr}").to_lowercase(),
45 });
46 }
47 }
48 }
49 ast::SelectItem::ExprWithAlias { expr, alias } => {
50 let sql_expr = convert_expr(expr)?;
51 result.push(Projection::Computed {
52 expr: sql_expr,
53 alias: normalize_ident(alias),
54 });
55 }
56 ast::SelectItem::Wildcard(_) => {
57 result.push(Projection::Star);
58 }
59 ast::SelectItem::QualifiedWildcard(kind, _) => {
60 let table_name = match kind {
61 ast::SelectItemQualifiedWildcardKind::ObjectName(name) => {
62 crate::parser::normalize::normalize_object_name_checked(name)?
63 }
64 _ => String::new(),
65 };
66 result.push(Projection::QualifiedStar(table_name));
67 }
68 }
69 }
70 Ok(result)
71}
72
73pub fn qualified_name(table: Option<&str>, name: &str) -> String {
75 table.map_or_else(|| name.to_string(), |table| format!("{table}.{name}"))
76}
77
78pub fn convert_where_to_filters(expr: &ast::Expr) -> Result<Vec<Filter>> {
80 let sql_expr = canonicalize_predicate(convert_expr(expr)?);
81 Ok(vec![Filter {
82 expr: FilterExpr::Expr(sql_expr),
83 }])
84}
85
86fn canonicalize_predicate(expr: SqlExpr) -> SqlExpr {
100 match expr {
101 SqlExpr::BinaryOp { left, op, right } => {
102 let left = canonicalize_predicate(*left);
103 let right = canonicalize_predicate(*right);
104 if let Some(flipped) = flip_comparison(op)
108 && !matches!(left, SqlExpr::Column { .. })
109 && matches!(right, SqlExpr::Column { .. })
110 {
111 return SqlExpr::BinaryOp {
112 left: Box::new(right),
113 op: flipped,
114 right: Box::new(left),
115 };
116 }
117 SqlExpr::BinaryOp {
118 left: Box::new(left),
119 op,
120 right: Box::new(right),
121 }
122 }
123 SqlExpr::UnaryOp { op, expr } => SqlExpr::UnaryOp {
124 op,
125 expr: Box::new(canonicalize_predicate(*expr)),
126 },
127 other => other,
128 }
129}
130
131fn flip_comparison(op: BinaryOp) -> Option<BinaryOp> {
135 match op {
136 BinaryOp::Eq => Some(BinaryOp::Eq),
137 BinaryOp::Ne => Some(BinaryOp::Ne),
138 BinaryOp::Gt => Some(BinaryOp::Lt),
139 BinaryOp::Ge => Some(BinaryOp::Le),
140 BinaryOp::Lt => Some(BinaryOp::Gt),
141 BinaryOp::Le => Some(BinaryOp::Ge),
142 BinaryOp::Add
143 | BinaryOp::Sub
144 | BinaryOp::Mul
145 | BinaryOp::Div
146 | BinaryOp::Mod
147 | BinaryOp::And
148 | BinaryOp::Or
149 | BinaryOp::Concat => None,
150 }
151}
152
153pub fn extract_func_args(func: &ast::Function) -> Result<Vec<ast::Expr>> {
154 match &func.args {
155 ast::FunctionArguments::List(args) => Ok(args
156 .args
157 .iter()
158 .filter_map(|a| match a {
159 ast::FunctionArg::Unnamed(ast::FunctionArgExpr::Expr(e)) => Some(e.clone()),
160 _ => None,
161 })
162 .collect()),
163 _ => Ok(Vec::new()),
164 }
165}
166
167pub(crate) fn eval_constant_expr(expr: &SqlExpr, functions: &FunctionRegistry) -> SqlValue {
172 crate::planner::const_fold::fold_constant(expr, functions).unwrap_or(SqlValue::Null)
173}
174
175pub(super) fn extract_geometry_arg(expr: &ast::Expr) -> Result<String> {
178 match expr {
179 ast::Expr::Function(func) => {
181 let name = func
182 .name
183 .0
184 .iter()
185 .map(|p| match p {
186 ast::ObjectNamePart::Identifier(ident) => normalize_ident(ident),
187 _ => String::new(),
188 })
189 .collect::<Vec<_>>()
190 .join(".");
191 let args = extract_func_args(func)?;
192 match name.as_str() {
193 "st_point" if args.len() >= 2 => {
194 let lon = extract_float(&args[0])?;
195 let lat = extract_float(&args[1])?;
196 Ok(format!(r#"{{"type":"Point","coordinates":[{lon},{lat}]}}"#))
197 }
198 "st_geomfromgeojson" if !args.is_empty() => extract_string_literal(&args[0]),
199 _ => Ok(format!("{expr}")),
200 }
201 }
202 _ => extract_string_literal(expr).or_else(|_| Ok(format!("{expr}"))),
204 }
205}
206
207pub(super) fn extract_column_name(expr: &ast::Expr) -> Result<String> {
208 match expr {
209 ast::Expr::Identifier(ident) => Ok(normalize_ident(ident)),
210 ast::Expr::CompoundIdentifier(parts) if parts.len() >= 3 => {
211 let qualified: String = parts
212 .iter()
213 .map(normalize_ident)
214 .collect::<Vec<_>>()
215 .join(".");
216 Err(SqlError::Unsupported {
217 detail: format!(
218 "schema-qualified column reference '{qualified}': {SCHEMA_QUALIFIED_MSG}"
219 ),
220 })
221 }
222 ast::Expr::CompoundIdentifier(parts) => Ok(parts
223 .iter()
224 .map(normalize_ident)
225 .collect::<Vec<_>>()
226 .join(".")),
227 _ => Err(SqlError::Unsupported {
228 detail: format!("expected column name, got: {expr}"),
229 }),
230 }
231}
232
233pub fn extract_string_literal(expr: &ast::Expr) -> Result<String> {
234 match expr {
235 ast::Expr::Value(v) => match &v.value {
236 ast::Value::SingleQuotedString(s) => Ok(s.clone()),
237 _ => Err(SqlError::Unsupported {
238 detail: format!("expected string literal, got: {expr}"),
239 }),
240 },
241 _ => Err(SqlError::Unsupported {
242 detail: format!("expected string literal, got: {expr}"),
243 }),
244 }
245}
246
247pub fn extract_float(expr: &ast::Expr) -> Result<f64> {
248 match expr {
249 ast::Expr::Value(v) => match &v.value {
250 ast::Value::Number(n, _) => n.parse::<f64>().map_err(|_| SqlError::TypeMismatch {
251 detail: format!("expected number: {n}"),
252 }),
253 _ => Err(SqlError::TypeMismatch {
254 detail: format!("expected number, got: {expr}"),
255 }),
256 },
257 ast::Expr::UnaryOp {
259 op: ast::UnaryOperator::Minus,
260 expr: inner,
261 } => extract_float(inner).map(|f| -f),
262 _ => Err(SqlError::TypeMismatch {
263 detail: format!("expected number, got: {expr}"),
264 }),
265 }
266}
267
268pub(super) fn metric_from_func_name(name: &str) -> DistanceMetric {
276 if name.eq_ignore_ascii_case("vector_cosine_distance") {
277 DistanceMetric::Cosine
278 } else if name.eq_ignore_ascii_case("vector_neg_inner_product") {
279 DistanceMetric::InnerProduct
280 } else {
281 DistanceMetric::L2
282 }
283}
284
285pub(super) fn extract_float_array(expr: &ast::Expr) -> Result<Vec<f32>> {
288 match expr {
289 ast::Expr::Array(ast::Array { elem, .. }) => elem
290 .iter()
291 .map(|e| extract_float(e).map(|f| f as f32))
292 .collect(),
293 ast::Expr::Function(func) => {
294 let name = func
295 .name
296 .0
297 .iter()
298 .map(|p| match p {
299 ast::ObjectNamePart::Identifier(ident) => normalize_ident(ident),
300 _ => String::new(),
301 })
302 .collect::<Vec<_>>()
303 .join(".");
304 if name == "make_array" || name == "array" {
305 let args = extract_func_args(func)?;
306 args.iter()
307 .map(|e| extract_float(e).map(|f| f as f32))
308 .collect()
309 } else {
310 Err(SqlError::Unsupported {
311 detail: format!("expected array, got function: {name}"),
312 })
313 }
314 }
315 ast::Expr::Value(v) => {
319 let s = match &v.value {
320 sqlparser::ast::Value::SingleQuotedString(s) => s.as_str(),
321 sqlparser::ast::Value::DoubleQuotedString(s) => s.as_str(),
322 _ => {
323 return Err(SqlError::Unsupported {
324 detail: format!("expected array literal, got: {expr}"),
325 });
326 }
327 };
328 let trimmed = s.trim();
329 if !trimmed.starts_with('[') || !trimmed.ends_with(']') {
330 return Err(SqlError::Unsupported {
331 detail: format!("expected JSON array string, got: {s:?}"),
332 });
333 }
334 let inner = &trimmed[1..trimmed.len() - 1];
335 if inner.trim().is_empty() {
336 return Ok(Vec::new());
337 }
338 inner
339 .split(',')
340 .map(|part| {
341 part.trim()
342 .parse::<f32>()
343 .map_err(|_| SqlError::Unsupported {
344 detail: format!("cannot parse float from array element: {part:?}"),
345 })
346 })
347 .collect()
348 }
349 _ => Err(SqlError::Unsupported {
350 detail: format!("expected array literal, got: {expr}"),
351 }),
352 }
353}