Skip to content

Commit c2f46ae

Browse files
adding support for scale in CEIL and FLOOR functions (#1377)
1 parent b072ce2 commit c2f46ae

File tree

3 files changed

+113
-19
lines changed

3 files changed

+113
-19
lines changed

src/ast/mod.rs

Lines changed: 36 additions & 12 deletions
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
@@ -674,16 +692,22 @@ pub enum Expr {
674692
/// ```sql
675693
/// CEIL(<expr> [TO DateTimeField])
676694
/// ```
695+
/// ```sql
696+
/// CEIL( <input_expr> [, <scale_expr> ] )
697+
/// ```
677698
Ceil {
678699
expr: Box<Expr>,
679-
field: DateTimeField,
700+
field: CeilFloorKind,
680701
},
681702
/// ```sql
682703
/// FLOOR(<expr> [TO DateTimeField])
683704
/// ```
705+
/// ```sql
706+
/// FLOOR( <input_expr> [, <scale_expr> ] )
707+
///
684708
Floor {
685709
expr: Box<Expr>,
686-
field: DateTimeField,
710+
field: CeilFloorKind,
687711
},
688712
/// ```sql
689713
/// POSITION(<expr> in <expr>)
@@ -1230,20 +1254,20 @@ impl fmt::Display for Expr {
12301254
ExtractSyntax::From => write!(f, "EXTRACT({field} FROM {expr})"),
12311255
ExtractSyntax::Comma => write!(f, "EXTRACT({field}, {expr})"),
12321256
},
1233-
Expr::Ceil { expr, field } => {
1234-
if field == &DateTimeField::NoDateTime {
1257+
Expr::Ceil { expr, field } => match field {
1258+
CeilFloorKind::DateTimeField(DateTimeField::NoDateTime) => {
12351259
write!(f, "CEIL({expr})")
1236-
} else {
1237-
write!(f, "CEIL({expr} TO {field})")
12381260
}
1239-
}
1240-
Expr::Floor { expr, field } => {
1241-
if field == &DateTimeField::NoDateTime {
1261+
CeilFloorKind::DateTimeField(dt_field) => write!(f, "CEIL({expr} TO {dt_field})"),
1262+
CeilFloorKind::Scale(s) => write!(f, "CEIL({expr}, {s})"),
1263+
},
1264+
Expr::Floor { expr, field } => match field {
1265+
CeilFloorKind::DateTimeField(DateTimeField::NoDateTime) => {
12421266
write!(f, "FLOOR({expr})")
1243-
} else {
1244-
write!(f, "FLOOR({expr} TO {field})")
12451267
}
1246-
}
1268+
CeilFloorKind::DateTimeField(dt_field) => write!(f, "FLOOR({expr} TO {dt_field})"),
1269+
CeilFloorKind::Scale(s) => write!(f, "FLOOR({expr}, {s})"),
1270+
},
12471271
Expr::Position { expr, r#in } => write!(f, "POSITION({expr} IN {in})"),
12481272
Expr::Collate { expr, collation } => write!(f, "{expr} COLLATE {collation}"),
12491273
Expr::Nested(ast) => write!(f, "({ast})"),

src/parser/mod.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,12 +1708,22 @@ impl<'a> Parser<'a> {
17081708
self.expect_token(&Token::LParen)?;
17091709
let expr = self.parse_expr()?;
17101710
// Parse `CEIL/FLOOR(expr)`
1711-
let mut field = DateTimeField::NoDateTime;
1712-
let keyword_to = self.parse_keyword(Keyword::TO);
1713-
if keyword_to {
1711+
let field = if self.parse_keyword(Keyword::TO) {
17141712
// Parse `CEIL/FLOOR(expr TO DateTimeField)`
1715-
field = self.parse_date_time_field()?;
1716-
}
1713+
CeilFloorKind::DateTimeField(self.parse_date_time_field()?)
1714+
} else if self.consume_token(&Token::Comma) {
1715+
// Parse `CEIL/FLOOR(expr, scale)`
1716+
match self.parse_value()? {
1717+
Value::Number(n, s) => CeilFloorKind::Scale(Value::Number(n, s)),
1718+
_ => {
1719+
return Err(ParserError::ParserError(
1720+
"Scale field can only be of number type".to_string(),
1721+
))
1722+
}
1723+
}
1724+
} else {
1725+
CeilFloorKind::DateTimeField(DateTimeField::NoDateTime)
1726+
};
17171727
self.expect_token(&Token::RParen)?;
17181728
if is_ceil {
17191729
Ok(Expr::Ceil {

tests/sqlparser_common.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,14 +2494,74 @@ fn parse_floor_number() {
24942494
verified_stmt("SELECT FLOOR(float_column) FROM my_table");
24952495
}
24962496

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

0 commit comments

Comments
 (0)