1use llkv_result::{Error, Result};
8use sqlparser::ast::{
9 Expr as SqlExpr, FunctionArg, FunctionArgExpr, GroupByExpr, ObjectName, ObjectNamePart, Select,
10 SelectItem, SelectItemQualifiedWildcardKind, TableAlias, TableFactor, UnaryOperator, Value,
11 ValueWithSpan,
12};
13
14use crate::PlanValue;
15
16pub fn plan_value_from_sql_expr(expr: &SqlExpr) -> Result<PlanValue> {
41 match expr {
42 SqlExpr::Value(value) => plan_value_from_sql_value(value),
43 SqlExpr::UnaryOp {
44 op: UnaryOperator::Minus,
45 expr,
46 } => match plan_value_from_sql_expr(expr)? {
47 PlanValue::Integer(v) => Ok(PlanValue::Integer(-v)),
48 PlanValue::Float(v) => Ok(PlanValue::Float(-v)),
49 PlanValue::Null | PlanValue::String(_) | PlanValue::Struct(_) => Err(
50 Error::InvalidArgumentError("cannot negate non-numeric literal".into()),
51 ),
52 },
53 SqlExpr::UnaryOp {
54 op: UnaryOperator::Plus,
55 expr,
56 } => plan_value_from_sql_expr(expr),
57 SqlExpr::Nested(inner) => plan_value_from_sql_expr(inner),
58 SqlExpr::Dictionary(fields) => {
59 let mut map = std::collections::HashMap::new();
60 for field in fields {
61 let key = field.key.value.clone();
62 let value = plan_value_from_sql_expr(&field.value)?;
63 map.insert(key, value);
64 }
65 Ok(PlanValue::Struct(map))
66 }
67 other => Err(Error::InvalidArgumentError(format!(
68 "unsupported literal expression: {other:?}"
69 ))),
70 }
71}
72
73pub fn plan_value_from_sql_value(value: &ValueWithSpan) -> Result<PlanValue> {
97 match &value.value {
98 Value::Null => Ok(PlanValue::Null),
99 Value::Number(text, _) => {
100 if text.contains(['.', 'e', 'E']) {
101 let parsed = text.parse::<f64>().map_err(|err| {
102 Error::InvalidArgumentError(format!("invalid float literal: {err}"))
103 })?;
104 Ok(PlanValue::Float(parsed))
105 } else {
106 let parsed = text.parse::<i64>().map_err(|err| {
107 Error::InvalidArgumentError(format!("invalid integer literal: {err}"))
108 })?;
109 Ok(PlanValue::Integer(parsed))
110 }
111 }
112 Value::Boolean(_) => Err(Error::InvalidArgumentError(
113 "BOOLEAN literals are not supported yet".into(),
114 )),
115 other => {
116 if let Some(text) = other.clone().into_string() {
117 Ok(PlanValue::String(text))
118 } else {
119 Err(Error::InvalidArgumentError(format!(
120 "unsupported literal: {other:?}"
121 )))
122 }
123 }
124 }
125}
126
127#[derive(Clone)]
133pub struct RangeSelectRows {
134 rows: Vec<Vec<PlanValue>>,
135}
136
137impl RangeSelectRows {
138 pub fn into_rows(self) -> Vec<Vec<PlanValue>> {
140 self.rows
141 }
142}
143
144#[derive(Clone)]
145enum RangeProjection {
146 Column,
147 Literal(PlanValue),
148}
149
150#[derive(Clone)]
151struct RangeSpec {
152 start: i64,
153 #[allow(dead_code)] end: i64,
155 row_count: usize,
156 column_name_lower: String,
157 table_alias_lower: Option<String>,
158}
159
160impl RangeSpec {
161 fn matches_identifier(&self, ident: &str) -> bool {
162 let lower = ident.to_ascii_lowercase();
163 lower == self.column_name_lower || lower == "range"
164 }
165
166 fn matches_table_alias(&self, ident: &str) -> bool {
167 let lower = ident.to_ascii_lowercase();
168 match &self.table_alias_lower {
169 Some(alias) => lower == *alias,
170 None => lower == "range",
171 }
172 }
173
174 fn matches_object_name(&self, name: &ObjectName) -> bool {
175 if name.0.len() != 1 {
176 return false;
177 }
178 match &name.0[0] {
179 ObjectNamePart::Identifier(ident) => self.matches_table_alias(&ident.value),
180 _ => false,
181 }
182 }
183}
184
185pub fn extract_rows_from_range(select: &Select) -> Result<Option<RangeSelectRows>> {
201 let spec = match parse_range_spec(select)? {
202 Some(spec) => spec,
203 None => return Ok(None),
204 };
205
206 if select.selection.is_some() {
207 return Err(Error::InvalidArgumentError(
208 "WHERE clauses are not supported for range() SELECT statements".into(),
209 ));
210 }
211 if select.having.is_some()
212 || !select.named_window.is_empty()
213 || select.qualify.is_some()
214 || select.distinct.is_some()
215 || select.top.is_some()
216 || select.into.is_some()
217 || select.prewhere.is_some()
218 || !select.lateral_views.is_empty()
219 || select.value_table_mode.is_some()
220 || !group_by_is_empty(&select.group_by)
221 {
222 return Err(Error::InvalidArgumentError(
223 "advanced SELECT clauses are not supported for range() SELECT statements".into(),
224 ));
225 }
226
227 let mut projections: Vec<RangeProjection> = Vec::with_capacity(select.projection.len());
228
229 if select.projection.is_empty() {
231 projections.push(RangeProjection::Column);
232 } else {
233 for item in &select.projection {
234 let projection = match item {
235 SelectItem::Wildcard(_) => RangeProjection::Column,
236 SelectItem::QualifiedWildcard(kind, _) => match kind {
237 SelectItemQualifiedWildcardKind::ObjectName(object_name) => {
238 if spec.matches_object_name(object_name) {
239 RangeProjection::Column
240 } else {
241 return Err(Error::InvalidArgumentError(
242 "qualified wildcard must reference the range() source".into(),
243 ));
244 }
245 }
246 SelectItemQualifiedWildcardKind::Expr(_) => {
247 return Err(Error::InvalidArgumentError(
248 "expression-qualified wildcards are not supported for range() SELECT statements".into(),
249 ));
250 }
251 },
252 SelectItem::UnnamedExpr(expr) => build_range_projection_expr(expr, &spec)?,
253 SelectItem::ExprWithAlias { expr, .. } => build_range_projection_expr(expr, &spec)?,
254 };
255 projections.push(projection);
256 }
257 }
258
259 let mut rows: Vec<Vec<PlanValue>> = Vec::with_capacity(spec.row_count);
260 for idx in 0..spec.row_count {
261 let mut row: Vec<PlanValue> = Vec::with_capacity(projections.len());
262 let value = spec.start + (idx as i64);
263 for projection in &projections {
264 match projection {
265 RangeProjection::Column => row.push(PlanValue::Integer(value)),
266 RangeProjection::Literal(value) => row.push(value.clone()),
267 }
268 }
269 rows.push(row);
270 }
271
272 Ok(Some(RangeSelectRows { rows }))
273}
274
275fn build_range_projection_expr(expr: &SqlExpr, spec: &RangeSpec) -> Result<RangeProjection> {
276 match expr {
277 SqlExpr::Identifier(ident) => {
278 if spec.matches_identifier(&ident.value) {
279 Ok(RangeProjection::Column)
280 } else {
281 Err(Error::InvalidArgumentError(format!(
282 "unknown column '{}' in range() SELECT",
283 ident.value
284 )))
285 }
286 }
287 SqlExpr::CompoundIdentifier(parts) => {
288 if parts.len() == 2
289 && spec.matches_table_alias(&parts[0].value)
290 && spec.matches_identifier(&parts[1].value)
291 {
292 Ok(RangeProjection::Column)
293 } else {
294 Err(Error::InvalidArgumentError(
295 "compound identifiers must reference the range() source".into(),
296 ))
297 }
298 }
299 SqlExpr::Wildcard(_) | SqlExpr::QualifiedWildcard(_, _) => unreachable!(),
300 other => Ok(RangeProjection::Literal(plan_value_from_sql_expr(other)?)),
301 }
302}
303
304fn parse_range_spec(select: &Select) -> Result<Option<RangeSpec>> {
305 if select.from.len() != 1 {
306 return Ok(None);
307 }
308 let item = &select.from[0];
309 if !item.joins.is_empty() {
310 return Err(Error::InvalidArgumentError(
311 "JOIN clauses are not supported for range() SELECT statements".into(),
312 ));
313 }
314
315 match &item.relation {
316 TableFactor::Function {
317 lateral,
318 name,
319 args,
320 alias,
321 } => {
322 if *lateral {
323 return Err(Error::InvalidArgumentError(
324 "LATERAL range() is not supported".into(),
325 ));
326 }
327 parse_range_spec_from_args(name, args, alias)
328 }
329 TableFactor::Table {
330 name,
331 alias,
332 args: Some(table_args),
333 with_ordinality,
334 ..
335 } => {
336 if *with_ordinality {
337 return Err(Error::InvalidArgumentError(
338 "WITH ORDINALITY is not supported for range()".into(),
339 ));
340 }
341 if table_args.settings.is_some() {
342 return Err(Error::InvalidArgumentError(
343 "range() SETTINGS clause is not supported".into(),
344 ));
345 }
346 parse_range_spec_from_args(name, &table_args.args, alias)
347 }
348 _ => Ok(None),
349 }
350}
351
352fn parse_range_spec_from_args(
353 name: &ObjectName,
354 args: &[FunctionArg],
355 alias: &Option<TableAlias>,
356) -> Result<Option<RangeSpec>> {
357 if name.0.len() != 1 {
358 return Ok(None);
359 }
360 let func_name = match &name.0[0] {
361 ObjectNamePart::Identifier(ident) => ident.value.to_ascii_lowercase(),
362 _ => return Ok(None),
363 };
364 if func_name != "range" {
365 return Ok(None);
366 }
367
368 if args.is_empty() || args.len() > 2 {
369 return Err(Error::InvalidArgumentError(
370 "range() requires one or two arguments".into(),
371 ));
372 }
373
374 let extract_int = |arg: &FunctionArg| -> Result<i64> {
376 let arg_expr = match arg {
377 FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
378 FunctionArg::Unnamed(FunctionArgExpr::QualifiedWildcard(_))
379 | FunctionArg::Unnamed(FunctionArgExpr::Wildcard) => {
380 return Err(Error::InvalidArgumentError(
381 "range() argument must be an integer literal".into(),
382 ));
383 }
384 FunctionArg::Named { .. } | FunctionArg::ExprNamed { .. } => {
385 return Err(Error::InvalidArgumentError(
386 "named arguments are not supported for range()".into(),
387 ));
388 }
389 };
390
391 let value = plan_value_from_sql_expr(arg_expr)?;
392 match value {
393 PlanValue::Integer(v) => Ok(v),
394 _ => Err(Error::InvalidArgumentError(
395 "range() argument must be an integer literal".into(),
396 )),
397 }
398 };
399
400 let (start, end, row_count) = if args.len() == 1 {
401 let count = extract_int(&args[0])?;
403 if count < 0 {
404 return Err(Error::InvalidArgumentError(
405 "range() argument must be non-negative".into(),
406 ));
407 }
408 (0, count, count as usize)
409 } else {
410 let start = extract_int(&args[0])?;
412 let end = extract_int(&args[1])?;
413 if end < start {
414 return Err(Error::InvalidArgumentError(
415 "range() end must be >= start".into(),
416 ));
417 }
418 let row_count = (end - start) as usize;
419 (start, end, row_count)
420 };
421
422 let column_name_lower = alias
423 .as_ref()
424 .and_then(|a| {
425 a.columns
426 .first()
427 .map(|col| col.name.value.to_ascii_lowercase())
428 })
429 .unwrap_or_else(|| "range".to_string());
430 let table_alias_lower = alias.as_ref().map(|a| a.name.value.to_ascii_lowercase());
431
432 Ok(Some(RangeSpec {
433 start,
434 end,
435 row_count,
436 column_name_lower,
437 table_alias_lower,
438 }))
439}
440
441fn group_by_is_empty(expr: &GroupByExpr) -> bool {
442 matches!(
443 expr,
444 GroupByExpr::Expressions(exprs, modifiers)
445 if exprs.is_empty() && modifiers.is_empty()
446 )
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use sqlparser::ast::{Expr as SqlExpr, Value, ValueWithSpan};
453
454 fn value_with_span(v: Value) -> ValueWithSpan {
455 ValueWithSpan {
456 value: v,
457 span: sqlparser::tokenizer::Span::empty(),
458 }
459 }
460
461 #[test]
462 fn test_null_value() {
463 let value = value_with_span(Value::Null);
464 assert_eq!(plan_value_from_sql_value(&value).unwrap(), PlanValue::Null);
465 }
466
467 #[test]
468 fn test_integer_value() {
469 let value = value_with_span(Value::Number("42".to_string(), false));
470 assert_eq!(
471 plan_value_from_sql_value(&value).unwrap(),
472 PlanValue::Integer(42)
473 );
474 }
475
476 #[test]
477 fn test_negative_integer() {
478 let value = value_with_span(Value::Number("42".to_string(), false));
479 let expr = SqlExpr::UnaryOp {
480 op: UnaryOperator::Minus,
481 expr: Box::new(SqlExpr::Value(value)),
482 };
483 assert_eq!(
484 plan_value_from_sql_expr(&expr).unwrap(),
485 PlanValue::Integer(-42)
486 );
487 }
488
489 #[test]
490 #[allow(clippy::approx_constant)]
491 fn test_float_value() {
492 let value = value_with_span(Value::Number("3.14".to_string(), false));
493 assert_eq!(
494 plan_value_from_sql_value(&value).unwrap(),
495 PlanValue::Float(3.14)
496 );
497 }
498
499 #[test]
500 fn test_string_value() {
501 let value = value_with_span(Value::SingleQuotedString("hello".to_string()));
502 assert_eq!(
503 plan_value_from_sql_value(&value).unwrap(),
504 PlanValue::String("hello".to_string())
505 );
506 }
507
508 #[test]
509 fn test_nested_expression() {
510 let value = value_with_span(Value::Number("100".to_string(), false));
511 let expr = SqlExpr::Nested(Box::new(SqlExpr::Value(value)));
512 assert_eq!(
513 plan_value_from_sql_expr(&expr).unwrap(),
514 PlanValue::Integer(100)
515 );
516 }
517
518 #[test]
519 fn test_plus_operator() {
520 let value = value_with_span(Value::Number("50".to_string(), false));
521 let expr = SqlExpr::UnaryOp {
522 op: UnaryOperator::Plus,
523 expr: Box::new(SqlExpr::Value(value)),
524 };
525 assert_eq!(
526 plan_value_from_sql_expr(&expr).unwrap(),
527 PlanValue::Integer(50)
528 );
529 }
530
531 #[test]
532 fn test_cannot_negate_string() {
533 let value = value_with_span(Value::SingleQuotedString("test".to_string()));
534 let expr = SqlExpr::UnaryOp {
535 op: UnaryOperator::Minus,
536 expr: Box::new(SqlExpr::Value(value)),
537 };
538 assert!(plan_value_from_sql_expr(&expr).is_err());
539 }
540
541 #[test]
542 fn test_boolean_not_supported() {
543 let value = value_with_span(Value::Boolean(true));
544 assert!(plan_value_from_sql_value(&value).is_err());
545 }
546}