1use llkv_expr::literal::Literal;
8use llkv_result::{Error, Result};
9use llkv_types::decimal::DecimalValue;
10use rustc_hash::FxHashMap;
11use sqlparser::ast::{
12 DataType, Expr as SqlExpr, FunctionArg, FunctionArgExpr, GroupByExpr, ObjectName,
13 ObjectNamePart, Select, SelectItem, SelectItemQualifiedWildcardKind, TableAlias, TableFactor,
14 TypedString, UnaryOperator, Value, ValueWithSpan,
15};
16
17use crate::PlanValue;
18use llkv_compute::date::parse_date32_literal;
19use llkv_compute::interval::parse_interval_literal;
20
21impl PlanValue {
22 pub fn from_operator_literal(op_value: &llkv_expr::literal::Literal) -> Option<PlanValue> {
23 match op_value {
24 Literal::Int128(v) => i64::try_from(*v).ok().map(PlanValue::Integer),
25 Literal::Float64(v) => Some(PlanValue::Float(*v)),
26 Literal::Boolean(v) => Some(PlanValue::Integer(if *v { 1 } else { 0 })),
27 Literal::String(v) => Some(PlanValue::String(v.clone())),
28 _ => None,
29 }
30 }
31
32 pub fn from_literal_for_join(literal: &Literal) -> Option<PlanValue> {
33 match literal {
34 Literal::Int128(v) => i64::try_from(*v).ok().map(PlanValue::Integer),
35 Literal::Float64(v) => Some(PlanValue::Float(*v)),
36 Literal::Boolean(v) => Some(PlanValue::Integer(if *v { 1 } else { 0 })),
37 Literal::String(v) => Some(PlanValue::String(v.clone())),
38 _ => None,
39 }
40 }
41
42 pub fn from_sql_expr(expr: &SqlExpr) -> Result<Self> {
44 match expr {
45 SqlExpr::Value(value) => Self::from_sql_value(value),
46 SqlExpr::TypedString(typed) => Self::from_typed_string(typed),
47 SqlExpr::Interval(interval) => {
48 let parsed = parse_interval_literal(interval)?;
49 Ok(PlanValue::Interval(parsed))
50 }
51 SqlExpr::UnaryOp {
52 op: UnaryOperator::Minus,
53 expr,
54 } => match Self::from_sql_expr(expr)? {
55 PlanValue::Integer(v) => Ok(PlanValue::Integer(-v)),
56 PlanValue::Float(v) => Ok(PlanValue::Float(-v)),
57 PlanValue::Decimal(value) => {
58 let negated = value.raw_value().checked_neg().ok_or_else(|| {
59 Error::InvalidArgumentError(
60 "decimal literal overflow when applying unary minus".into(),
61 )
62 })?;
63 let decimal = DecimalValue::new(negated, value.scale()).map_err(|err| {
64 Error::InvalidArgumentError(format!(
65 "failed to negate decimal literal: {err}"
66 ))
67 })?;
68 Ok(PlanValue::Decimal(decimal))
69 }
70 PlanValue::Null
71 | PlanValue::String(_)
72 | PlanValue::Struct(_)
73 | PlanValue::Date32(_)
74 | PlanValue::Interval(_) => Err(Error::InvalidArgumentError(
75 "cannot negate non-numeric literal".into(),
76 )),
77 },
78 SqlExpr::UnaryOp {
79 op: UnaryOperator::Plus,
80 expr,
81 } => Self::from_sql_expr(expr),
82 SqlExpr::Nested(inner) => Self::from_sql_expr(inner),
83 SqlExpr::Dictionary(fields) => {
84 let mut map = FxHashMap::with_capacity_and_hasher(fields.len(), Default::default());
85 for field in fields {
86 let key = field.key.value.clone();
87 let value = Self::from_sql_expr(&field.value)?;
88 map.insert(key, value);
89 }
90 Ok(PlanValue::Struct(map))
91 }
92 other => Err(Error::InvalidArgumentError(format!(
93 "unsupported literal expression: {other:?}"
94 ))),
95 }
96 }
97
98 pub fn from_sql_value(value: &ValueWithSpan) -> Result<Self> {
100 match &value.value {
101 Value::Null => Ok(PlanValue::Null),
102 Value::Number(text, _) => {
103 if text.contains(['.', 'e', 'E']) {
104 let parsed = text.parse::<f64>().map_err(|err| {
105 Error::InvalidArgumentError(format!("invalid float literal: {err}"))
106 })?;
107 Ok(PlanValue::Float(parsed))
108 } else {
109 let parsed = text.parse::<i64>().map_err(|err| {
110 Error::InvalidArgumentError(format!("invalid integer literal: {err}"))
111 })?;
112 Ok(PlanValue::Integer(parsed))
113 }
114 }
115 Value::Boolean(_) => Err(Error::InvalidArgumentError(
116 "BOOLEAN literals are not supported yet".into(),
117 )),
118 other => {
119 if let Some(text) = other.clone().into_string() {
120 Ok(PlanValue::String(text))
121 } else {
122 Err(Error::InvalidArgumentError(format!(
123 "unsupported literal: {other:?}"
124 )))
125 }
126 }
127 }
128 }
129
130 fn from_typed_string(typed: &TypedString) -> Result<Self> {
132 let text = typed.value.value.clone().into_string().ok_or_else(|| {
133 Error::InvalidArgumentError("typed string literal must be a quoted string".into())
134 })?;
135
136 match typed.data_type {
137 DataType::Date => {
138 let days = parse_date32_literal(&text)?;
139 Ok(PlanValue::Date32(days))
140 }
141 _ => Ok(PlanValue::String(text)),
142 }
143 }
144}
145
146#[derive(Clone)]
152pub struct RangeSelectRows {
153 rows: Vec<Vec<PlanValue>>,
154}
155
156impl RangeSelectRows {
157 pub fn into_rows(self) -> Vec<Vec<PlanValue>> {
159 self.rows
160 }
161}
162
163#[derive(Clone)]
164enum RangeProjection {
165 Column,
166 Literal(PlanValue),
167}
168
169#[derive(Clone)]
170struct RangeSpec {
171 start: i64,
172 #[allow(dead_code)] end: i64,
174 row_count: usize,
175 column_name_lower: String,
176 table_alias_lower: Option<String>,
177}
178
179impl RangeSpec {
180 fn matches_identifier(&self, ident: &str) -> bool {
181 let lower = ident.to_ascii_lowercase();
182 lower == self.column_name_lower || lower == "range"
183 }
184
185 fn matches_table_alias(&self, ident: &str) -> bool {
186 let lower = ident.to_ascii_lowercase();
187 match &self.table_alias_lower {
188 Some(alias) => lower == *alias,
189 None => lower == "range",
190 }
191 }
192
193 fn matches_object_name(&self, name: &ObjectName) -> bool {
194 if name.0.len() != 1 {
195 return false;
196 }
197 match &name.0[0] {
198 ObjectNamePart::Identifier(ident) => self.matches_table_alias(&ident.value),
199 _ => false,
200 }
201 }
202}
203
204pub fn extract_rows_from_range(select: &Select) -> Result<Option<RangeSelectRows>> {
220 let spec = match parse_range_spec(select)? {
221 Some(spec) => spec,
222 None => return Ok(None),
223 };
224
225 if select.selection.is_some() {
226 return Err(Error::InvalidArgumentError(
227 "WHERE clauses are not supported for range() SELECT statements".into(),
228 ));
229 }
230 if select.having.is_some()
231 || !select.named_window.is_empty()
232 || select.qualify.is_some()
233 || select.distinct.is_some()
234 || select.top.is_some()
235 || select.into.is_some()
236 || select.prewhere.is_some()
237 || !select.lateral_views.is_empty()
238 || select.value_table_mode.is_some()
239 || !group_by_is_empty(&select.group_by)
240 {
241 return Err(Error::InvalidArgumentError(
242 "advanced SELECT clauses are not supported for range() SELECT statements".into(),
243 ));
244 }
245
246 let mut projections: Vec<RangeProjection> = Vec::with_capacity(select.projection.len());
247
248 if select.projection.is_empty() {
250 projections.push(RangeProjection::Column);
251 } else {
252 for item in &select.projection {
253 let projection = match item {
254 SelectItem::Wildcard(_) => RangeProjection::Column,
255 SelectItem::QualifiedWildcard(kind, _) => match kind {
256 SelectItemQualifiedWildcardKind::ObjectName(object_name) => {
257 if spec.matches_object_name(object_name) {
258 RangeProjection::Column
259 } else {
260 return Err(Error::InvalidArgumentError(
261 "qualified wildcard must reference the range() source".into(),
262 ));
263 }
264 }
265 SelectItemQualifiedWildcardKind::Expr(_) => {
266 return Err(Error::InvalidArgumentError(
267 "expression-qualified wildcards are not supported for range() SELECT statements".into(),
268 ));
269 }
270 },
271 SelectItem::UnnamedExpr(expr) => build_range_projection_expr(expr, &spec)?,
272 SelectItem::ExprWithAlias { expr, .. } => build_range_projection_expr(expr, &spec)?,
273 };
274 projections.push(projection);
275 }
276 }
277
278 let mut rows: Vec<Vec<PlanValue>> = Vec::with_capacity(spec.row_count);
279 for idx in 0..spec.row_count {
280 let mut row: Vec<PlanValue> = Vec::with_capacity(projections.len());
281 let value = spec.start + (idx as i64);
282 for projection in &projections {
283 match projection {
284 RangeProjection::Column => row.push(PlanValue::Integer(value)),
285 RangeProjection::Literal(value) => row.push(value.clone()),
286 }
287 }
288 rows.push(row);
289 }
290
291 Ok(Some(RangeSelectRows { rows }))
292}
293
294fn build_range_projection_expr(expr: &SqlExpr, spec: &RangeSpec) -> Result<RangeProjection> {
295 match expr {
296 SqlExpr::Identifier(ident) => {
297 if spec.matches_identifier(&ident.value) {
298 Ok(RangeProjection::Column)
299 } else {
300 Err(Error::InvalidArgumentError(format!(
301 "unknown column '{}' in range() SELECT",
302 ident.value
303 )))
304 }
305 }
306 SqlExpr::CompoundIdentifier(parts) => {
307 if parts.len() == 2
308 && spec.matches_table_alias(&parts[0].value)
309 && spec.matches_identifier(&parts[1].value)
310 {
311 Ok(RangeProjection::Column)
312 } else {
313 Err(Error::InvalidArgumentError(
314 "compound identifiers must reference the range() source".into(),
315 ))
316 }
317 }
318 SqlExpr::Wildcard(_) | SqlExpr::QualifiedWildcard(_, _) => unreachable!(),
319 other => Ok(RangeProjection::Literal(PlanValue::from_sql_expr(other)?)),
320 }
321}
322
323fn parse_range_spec(select: &Select) -> Result<Option<RangeSpec>> {
324 if select.from.len() != 1 {
325 return Ok(None);
326 }
327 let item = &select.from[0];
328 if !item.joins.is_empty() {
329 return Err(Error::InvalidArgumentError(
330 "JOIN clauses are not supported for range() SELECT statements".into(),
331 ));
332 }
333
334 match &item.relation {
335 TableFactor::Function {
336 lateral,
337 name,
338 args,
339 alias,
340 } => {
341 if *lateral {
342 return Err(Error::InvalidArgumentError(
343 "LATERAL range() is not supported".into(),
344 ));
345 }
346 parse_range_spec_from_args(name, args, alias)
347 }
348 TableFactor::Table {
349 name,
350 alias,
351 args: Some(table_args),
352 with_ordinality,
353 ..
354 } => {
355 if *with_ordinality {
356 return Err(Error::InvalidArgumentError(
357 "WITH ORDINALITY is not supported for range()".into(),
358 ));
359 }
360 if table_args.settings.is_some() {
361 return Err(Error::InvalidArgumentError(
362 "range() SETTINGS clause is not supported".into(),
363 ));
364 }
365 parse_range_spec_from_args(name, &table_args.args, alias)
366 }
367 _ => Ok(None),
368 }
369}
370
371fn parse_range_spec_from_args(
372 name: &ObjectName,
373 args: &[FunctionArg],
374 alias: &Option<TableAlias>,
375) -> Result<Option<RangeSpec>> {
376 if name.0.len() != 1 {
377 return Ok(None);
378 }
379 let func_name = match &name.0[0] {
380 ObjectNamePart::Identifier(ident) => ident.value.to_ascii_lowercase(),
381 _ => return Ok(None),
382 };
383 if func_name != "range" {
384 return Ok(None);
385 }
386
387 if args.is_empty() || args.len() > 2 {
388 return Err(Error::InvalidArgumentError(
389 "range() requires one or two arguments".into(),
390 ));
391 }
392
393 let extract_int = |arg: &FunctionArg| -> Result<i64> {
395 let arg_expr = match arg {
396 FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
397 FunctionArg::Unnamed(FunctionArgExpr::QualifiedWildcard(_))
398 | FunctionArg::Unnamed(FunctionArgExpr::Wildcard) => {
399 return Err(Error::InvalidArgumentError(
400 "range() argument must be an integer literal".into(),
401 ));
402 }
403 FunctionArg::Named { .. } | FunctionArg::ExprNamed { .. } => {
404 return Err(Error::InvalidArgumentError(
405 "named arguments are not supported for range()".into(),
406 ));
407 }
408 };
409
410 let value = PlanValue::from_sql_expr(arg_expr)?;
411 match value {
412 PlanValue::Integer(v) => Ok(v),
413 _ => Err(Error::InvalidArgumentError(
414 "range() argument must be an integer literal".into(),
415 )),
416 }
417 };
418
419 let (start, end, row_count) = if args.len() == 1 {
420 let count = extract_int(&args[0])?;
422 if count < 0 {
423 return Err(Error::InvalidArgumentError(
424 "range() argument must be non-negative".into(),
425 ));
426 }
427 (0, count, count as usize)
428 } else {
429 let start = extract_int(&args[0])?;
431 let end = extract_int(&args[1])?;
432 if end < start {
433 return Err(Error::InvalidArgumentError(
434 "range() end must be >= start".into(),
435 ));
436 }
437 let row_count = (end - start) as usize;
438 (start, end, row_count)
439 };
440
441 let column_name_lower = alias
442 .as_ref()
443 .and_then(|a| {
444 a.columns
445 .first()
446 .map(|col| col.name.value.to_ascii_lowercase())
447 })
448 .unwrap_or_else(|| "range".to_string());
449 let table_alias_lower = alias.as_ref().map(|a| a.name.value.to_ascii_lowercase());
450
451 Ok(Some(RangeSpec {
452 start,
453 end,
454 row_count,
455 column_name_lower,
456 table_alias_lower,
457 }))
458}
459
460fn group_by_is_empty(expr: &GroupByExpr) -> bool {
461 matches!(
462 expr,
463 GroupByExpr::Expressions(exprs, modifiers)
464 if exprs.is_empty() && modifiers.is_empty()
465 )
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471 use llkv_types::IntervalValue;
472 use sqlparser::ast::{Expr as SqlExpr, SelectItem, SetExpr, Statement, Value, ValueWithSpan};
473 use sqlparser::dialect::GenericDialect;
474 use sqlparser::parser::Parser;
475
476 fn value_with_span(v: Value) -> ValueWithSpan {
477 ValueWithSpan {
478 value: v,
479 span: sqlparser::tokenizer::Span::empty(),
480 }
481 }
482
483 #[test]
484 fn test_null_value() {
485 let value = value_with_span(Value::Null);
486 assert_eq!(PlanValue::from_sql_value(&value).unwrap(), PlanValue::Null);
487 }
488
489 #[test]
490 fn test_integer_value() {
491 let value = value_with_span(Value::Number("42".to_string(), false));
492 assert_eq!(
493 PlanValue::from_sql_value(&value).unwrap(),
494 PlanValue::Integer(42)
495 );
496 }
497
498 #[test]
499 fn test_negative_integer() {
500 let value = value_with_span(Value::Number("42".to_string(), false));
501 let expr = SqlExpr::UnaryOp {
502 op: UnaryOperator::Minus,
503 expr: Box::new(SqlExpr::Value(value)),
504 };
505 assert_eq!(
506 PlanValue::from_sql_expr(&expr).unwrap(),
507 PlanValue::Integer(-42)
508 );
509 }
510
511 #[test]
512 #[allow(clippy::approx_constant)]
513 fn test_float_value() {
514 let value = value_with_span(Value::Number("3.14".to_string(), false));
515 assert_eq!(
516 PlanValue::from_sql_value(&value).unwrap(),
517 PlanValue::Float(3.14)
518 );
519 }
520
521 #[test]
522 fn test_string_value() {
523 let value = value_with_span(Value::SingleQuotedString("hello".to_string()));
524 assert_eq!(
525 PlanValue::from_sql_value(&value).unwrap(),
526 PlanValue::String("hello".to_string())
527 );
528 }
529
530 #[test]
531 fn test_nested_expression() {
532 let value = value_with_span(Value::Number("100".to_string(), false));
533 let expr = SqlExpr::Nested(Box::new(SqlExpr::Value(value)));
534 assert_eq!(
535 PlanValue::from_sql_expr(&expr).unwrap(),
536 PlanValue::Integer(100)
537 );
538 }
539
540 #[test]
541 fn test_plus_operator() {
542 let value = value_with_span(Value::Number("50".to_string(), false));
543 let expr = SqlExpr::UnaryOp {
544 op: UnaryOperator::Plus,
545 expr: Box::new(SqlExpr::Value(value)),
546 };
547 assert_eq!(
548 PlanValue::from_sql_expr(&expr).unwrap(),
549 PlanValue::Integer(50)
550 );
551 }
552
553 #[test]
554 fn test_interval_literal_expression() {
555 let dialect = GenericDialect {};
556 let statements = Parser::parse_sql(&dialect, "SELECT INTERVAL '7' DAY").unwrap();
557 let statement = statements.first().expect("statement");
558 let expr = match statement {
559 Statement::Query(query) => match query.body.as_ref() {
560 SetExpr::Select(select) => match select.projection.first().expect("projection") {
561 SelectItem::UnnamedExpr(expr) => expr,
562 other => panic!("unexpected projection {other:?}"),
563 },
564 other => panic!("unexpected set expr {other:?}"),
565 },
566 other => panic!("unexpected statement {other:?}"),
567 };
568
569 assert_eq!(
570 PlanValue::from_sql_expr(expr).unwrap(),
571 PlanValue::Interval(IntervalValue::new(0, 7, 0))
572 );
573 }
574
575 #[test]
576 fn test_cannot_negate_string() {
577 let value = value_with_span(Value::SingleQuotedString("test".to_string()));
578 let expr = SqlExpr::UnaryOp {
579 op: UnaryOperator::Minus,
580 expr: Box::new(SqlExpr::Value(value)),
581 };
582 assert!(PlanValue::from_sql_expr(&expr).is_err());
583 }
584
585 #[test]
586 fn test_boolean_not_supported() {
587 let value = value_with_span(Value::Boolean(true));
588 assert!(PlanValue::from_sql_value(&value).is_err());
589 }
590}