Skip to content

Commit 0b1b66a

Browse files
seve-martinezayman-sigma
authored andcommitted
adding support for scale in CEIL and FLOOR functions (apache#1377)
1 parent a863696 commit 0b1b66a

File tree

3 files changed

+113
-19
lines changed

3 files changed

+113
-19
lines changed

src/ast/mod.rs

+36-12
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,24 @@ pub enum ExtractSyntax {
500500
Comma,
501501
}
502502

503+
/// The syntax used in a CEIL or FLOOR expression.
504+
///
505+
/// The `CEIL/FLOOR(<datetime value expression> TO <time unit>)` is an Amazon Kinesis Data Analytics extension.
506+
/// See <https://docs.aws.amazon.com/kinesisanalytics/latest/sqlref/sql-reference-ceil.html> for
507+
/// details.
508+
///
509+
/// Other dialects either support `CEIL/FLOOR( <expr> [, <scale>])` format or just
510+
/// `CEIL/FLOOR(<expr>)`.
511+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
512+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
513+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
514+
pub enum CeilFloorKind {
515+
/// `CEIL( <expr> TO <DateTimeField>)`
516+
DateTimeField(DateTimeField),
517+
/// `CEIL( <expr> [, <scale>])`
518+
Scale(Value),
519+
}
520+
503521
/// An SQL expression of any type.
504522
///
505523
/// The parser does not distinguish between expressions of different types
@@ -683,16 +701,22 @@ pub enum Expr {
683701
/// ```sql
684702
/// CEIL(<expr> [TO DateTimeField])
685703
/// ```
704+
/// ```sql
705+
/// CEIL( <input_expr> [, <scale_expr> ] )
706+
/// ```
686707
Ceil {
687708
expr: Box<Expr>,
688-
field: DateTimeField,
709+
field: CeilFloorKind,
689710
},
690711
/// ```sql
691712
/// FLOOR(<expr> [TO DateTimeField])
692713
/// ```
714+
/// ```sql
715+
/// FLOOR( <input_expr> [, <scale_expr> ] )
716+
///
693717
Floor {
694718
expr: Box<Expr>,
695-
field: DateTimeField,
719+
field: CeilFloorKind,
696720
},
697721
/// ```sql
698722
/// POSITION(<expr> in <expr>)
@@ -1251,20 +1275,20 @@ impl fmt::Display for Expr {
12511275
ExtractSyntax::From => write!(f, "EXTRACT({field} FROM {expr})"),
12521276
ExtractSyntax::Comma => write!(f, "EXTRACT({field}, {expr})"),
12531277
},
1254-
Expr::Ceil { expr, field } => {
1255-
if field == &DateTimeField::NoDateTime {
1278+
Expr::Ceil { expr, field } => match field {
1279+
CeilFloorKind::DateTimeField(DateTimeField::NoDateTime) => {
12561280
write!(f, "CEIL({expr})")
1257-
} else {
1258-
write!(f, "CEIL({expr} TO {field})")
12591281
}
1260-
}
1261-
Expr::Floor { expr, field } => {
1262-
if field == &DateTimeField::NoDateTime {
1282+
CeilFloorKind::DateTimeField(dt_field) => write!(f, "CEIL({expr} TO {dt_field})"),
1283+
CeilFloorKind::Scale(s) => write!(f, "CEIL({expr}, {s})"),
1284+
},
1285+
Expr::Floor { expr, field } => match field {
1286+
CeilFloorKind::DateTimeField(DateTimeField::NoDateTime) => {
12631287
write!(f, "FLOOR({expr})")
1264-
} else {
1265-
write!(f, "FLOOR({expr} TO {field})")
12661288
}
1267-
}
1289+
CeilFloorKind::DateTimeField(dt_field) => write!(f, "FLOOR({expr} TO {dt_field})"),
1290+
CeilFloorKind::Scale(s) => write!(f, "FLOOR({expr}, {s})"),
1291+
},
12681292
Expr::Position { expr, r#in } => write!(f, "POSITION({expr} IN {in})"),
12691293
Expr::Collate { expr, collation } => write!(f, "{expr} COLLATE {collation}"),
12701294
Expr::Nested(ast) => write!(f, "({ast})"),

src/parser/mod.rs

+15-5
Original file line numberDiff line numberDiff line change
@@ -1712,12 +1712,22 @@ impl<'a> Parser<'a> {
17121712
self.expect_token(&Token::LParen)?;
17131713
let expr = self.parse_expr()?;
17141714
// Parse `CEIL/FLOOR(expr)`
1715-
let mut field = DateTimeField::NoDateTime;
1716-
let keyword_to = self.parse_keyword(Keyword::TO);
1717-
if keyword_to {
1715+
let field = if self.parse_keyword(Keyword::TO) {
17181716
// Parse `CEIL/FLOOR(expr TO DateTimeField)`
1719-
field = self.parse_date_time_field()?;
1720-
}
1717+
CeilFloorKind::DateTimeField(self.parse_date_time_field()?)
1718+
} else if self.consume_token(&Token::Comma) {
1719+
// Parse `CEIL/FLOOR(expr, scale)`
1720+
match self.parse_value()? {
1721+
Value::Number(n, s) => CeilFloorKind::Scale(Value::Number(n, s)),
1722+
_ => {
1723+
return Err(ParserError::ParserError(
1724+
"Scale field can only be of number type".to_string(),
1725+
))
1726+
}
1727+
}
1728+
} else {
1729+
CeilFloorKind::DateTimeField(DateTimeField::NoDateTime)
1730+
};
17211731
self.expect_token(&Token::RParen)?;
17221732
if is_ceil {
17231733
Ok(Expr::Ceil {

tests/sqlparser_common.rs

+62-2
Original file line numberDiff line numberDiff line change
@@ -2495,14 +2495,74 @@ fn parse_floor_number() {
24952495
verified_stmt("SELECT FLOOR(float_column) FROM my_table");
24962496
}
24972497

2498+
#[test]
2499+
fn parse_ceil_number_scale() {
2500+
verified_stmt("SELECT CEIL(1.5, 1)");
2501+
verified_stmt("SELECT CEIL(float_column, 3) FROM my_table");
2502+
}
2503+
2504+
#[test]
2505+
fn parse_floor_number_scale() {
2506+
verified_stmt("SELECT FLOOR(1.5, 1)");
2507+
verified_stmt("SELECT FLOOR(float_column, 3) FROM my_table");
2508+
}
2509+
2510+
#[test]
2511+
fn parse_ceil_scale() {
2512+
let sql = "SELECT CEIL(d, 2)";
2513+
let select = verified_only_select(sql);
2514+
2515+
#[cfg(feature = "bigdecimal")]
2516+
assert_eq!(
2517+
&Expr::Ceil {
2518+
expr: Box::new(Expr::Identifier(Ident::new("d"))),
2519+
field: CeilFloorKind::Scale(Value::Number(bigdecimal::BigDecimal::from(2), false)),
2520+
},
2521+
expr_from_projection(only(&select.projection)),
2522+
);
2523+
2524+
#[cfg(not(feature = "bigdecimal"))]
2525+
assert_eq!(
2526+
&Expr::Ceil {
2527+
expr: Box::new(Expr::Identifier(Ident::new("d"))),
2528+
field: CeilFloorKind::Scale(Value::Number(2.to_string(), false)),
2529+
},
2530+
expr_from_projection(only(&select.projection)),
2531+
);
2532+
}
2533+
2534+
#[test]
2535+
fn parse_floor_scale() {
2536+
let sql = "SELECT FLOOR(d, 2)";
2537+
let select = verified_only_select(sql);
2538+
2539+
#[cfg(feature = "bigdecimal")]
2540+
assert_eq!(
2541+
&Expr::Floor {
2542+
expr: Box::new(Expr::Identifier(Ident::new("d"))),
2543+
field: CeilFloorKind::Scale(Value::Number(bigdecimal::BigDecimal::from(2), false)),
2544+
},
2545+
expr_from_projection(only(&select.projection)),
2546+
);
2547+
2548+
#[cfg(not(feature = "bigdecimal"))]
2549+
assert_eq!(
2550+
&Expr::Floor {
2551+
expr: Box::new(Expr::Identifier(Ident::new("d"))),
2552+
field: CeilFloorKind::Scale(Value::Number(2.to_string(), false)),
2553+
},
2554+
expr_from_projection(only(&select.projection)),
2555+
);
2556+
}
2557+
24982558
#[test]
24992559
fn parse_ceil_datetime() {
25002560
let sql = "SELECT CEIL(d TO DAY)";
25012561
let select = verified_only_select(sql);
25022562
assert_eq!(
25032563
&Expr::Ceil {
25042564
expr: Box::new(Expr::Identifier(Ident::new("d"))),
2505-
field: DateTimeField::Day,
2565+
field: CeilFloorKind::DateTimeField(DateTimeField::Day),
25062566
},
25072567
expr_from_projection(only(&select.projection)),
25082568
);
@@ -2529,7 +2589,7 @@ fn parse_floor_datetime() {
25292589
assert_eq!(
25302590
&Expr::Floor {
25312591
expr: Box::new(Expr::Identifier(Ident::new("d"))),
2532-
field: DateTimeField::Day,
2592+
field: CeilFloorKind::DateTimeField(DateTimeField::Day),
25332593
},
25342594
expr_from_projection(only(&select.projection)),
25352595
);

0 commit comments

Comments
 (0)