mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-04 06:18:17 +00:00
Support CEIL(expr TO DateTimeField)
and FLOOR(expr TO DateTimeField)
(#635)
* support ceil/floor to datetime * Update mod.rs * Update parser.rs * murphys law * Update sqlparser_common.rs * possible fix? * remove question mark * ceil to floor * Update mod.rs * Apply suggestions from code review Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org> * refactor into parse_ceil_floor_expr Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
parent
f7f14df4b1
commit
cb397d19f9
5 changed files with 134 additions and 6 deletions
|
@ -332,6 +332,16 @@ pub enum Expr {
|
|||
field: DateTimeField,
|
||||
expr: Box<Expr>,
|
||||
},
|
||||
/// CEIL(<expr> [TO DateTimeField])
|
||||
Ceil {
|
||||
expr: Box<Expr>,
|
||||
field: DateTimeField,
|
||||
},
|
||||
/// FLOOR(<expr> [TO DateTimeField])
|
||||
Floor {
|
||||
expr: Box<Expr>,
|
||||
field: DateTimeField,
|
||||
},
|
||||
/// POSITION(<expr> in <expr>)
|
||||
Position { expr: Box<Expr>, r#in: Box<Expr> },
|
||||
/// SUBSTRING(<expr> [FROM <expr>] [FOR <expr>])
|
||||
|
@ -584,6 +594,20 @@ impl fmt::Display for Expr {
|
|||
Expr::TryCast { expr, data_type } => write!(f, "TRY_CAST({} AS {})", expr, data_type),
|
||||
Expr::SafeCast { expr, data_type } => write!(f, "SAFE_CAST({} AS {})", expr, data_type),
|
||||
Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr),
|
||||
Expr::Ceil { expr, field } => {
|
||||
if field == &DateTimeField::NoDateTime {
|
||||
write!(f, "CEIL({})", expr)
|
||||
} else {
|
||||
write!(f, "CEIL({} TO {})", expr, field)
|
||||
}
|
||||
}
|
||||
Expr::Floor { expr, field } => {
|
||||
if field == &DateTimeField::NoDateTime {
|
||||
write!(f, "FLOOR({})", expr)
|
||||
} else {
|
||||
write!(f, "FLOOR({} TO {})", expr, field)
|
||||
}
|
||||
}
|
||||
Expr::Position { expr, r#in } => write!(f, "POSITION({} IN {})", expr, r#in),
|
||||
Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation),
|
||||
Expr::Nested(ast) => write!(f, "({})", ast),
|
||||
|
|
|
@ -83,14 +83,17 @@ pub enum DateTimeField {
|
|||
Isodow,
|
||||
Isoyear,
|
||||
Julian,
|
||||
Microsecond,
|
||||
Microseconds,
|
||||
Millenium,
|
||||
Millennium,
|
||||
Millisecond,
|
||||
Milliseconds,
|
||||
Quarter,
|
||||
Timezone,
|
||||
TimezoneHour,
|
||||
TimezoneMinute,
|
||||
NoDateTime,
|
||||
}
|
||||
|
||||
impl fmt::Display for DateTimeField {
|
||||
|
@ -111,14 +114,17 @@ impl fmt::Display for DateTimeField {
|
|||
DateTimeField::Isodow => "ISODOW",
|
||||
DateTimeField::Isoyear => "ISOYEAR",
|
||||
DateTimeField::Julian => "JULIAN",
|
||||
DateTimeField::Microsecond => "MICROSECOND",
|
||||
DateTimeField::Microseconds => "MICROSECONDS",
|
||||
DateTimeField::Millenium => "MILLENIUM",
|
||||
DateTimeField::Millennium => "MILLENNIUM",
|
||||
DateTimeField::Millisecond => "MILLISECOND",
|
||||
DateTimeField::Milliseconds => "MILLISECONDS",
|
||||
DateTimeField::Quarter => "QUARTER",
|
||||
DateTimeField::Timezone => "TIMEZONE",
|
||||
DateTimeField::TimezoneHour => "TIMEZONE_HOUR",
|
||||
DateTimeField::TimezoneMinute => "TIMEZONE_MINUTE",
|
||||
DateTimeField::NoDateTime => "NODATETIME",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -330,9 +330,11 @@ define_keywords!(
|
|||
MERGE,
|
||||
METADATA,
|
||||
METHOD,
|
||||
MICROSECOND,
|
||||
MICROSECONDS,
|
||||
MILLENIUM,
|
||||
MILLENNIUM,
|
||||
MILLISECOND,
|
||||
MILLISECONDS,
|
||||
MIN,
|
||||
MINUTE,
|
||||
|
|
|
@ -450,6 +450,8 @@ impl<'a> Parser<'a> {
|
|||
Keyword::SAFE_CAST => self.parse_safe_cast_expr(),
|
||||
Keyword::EXISTS => self.parse_exists_expr(false),
|
||||
Keyword::EXTRACT => self.parse_extract_expr(),
|
||||
Keyword::CEIL => self.parse_ceil_floor_expr(true),
|
||||
Keyword::FLOOR => self.parse_ceil_floor_expr(false),
|
||||
Keyword::POSITION => self.parse_position_expr(),
|
||||
Keyword::SUBSTRING => self.parse_substring_expr(),
|
||||
Keyword::OVERLAY => self.parse_overlay_expr(),
|
||||
|
@ -848,6 +850,30 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn parse_ceil_floor_expr(&mut self, is_ceil: bool) -> Result<Expr, ParserError> {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let expr = self.parse_expr()?;
|
||||
// Parse `CEIL/FLOOR(expr)`
|
||||
let mut field = DateTimeField::NoDateTime;
|
||||
let keyword_to = self.parse_keyword(Keyword::TO);
|
||||
if keyword_to {
|
||||
// Parse `CEIL/FLOOR(expr TO DateTimeField)`
|
||||
field = self.parse_date_time_field()?;
|
||||
}
|
||||
self.expect_token(&Token::RParen)?;
|
||||
if is_ceil {
|
||||
Ok(Expr::Ceil {
|
||||
expr: Box::new(expr),
|
||||
field,
|
||||
})
|
||||
} else {
|
||||
Ok(Expr::Floor {
|
||||
expr: Box::new(expr),
|
||||
field,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_position_expr(&mut self) -> Result<Expr, ParserError> {
|
||||
// PARSE SELECT POSITION('@' in field)
|
||||
self.expect_token(&Token::LParen)?;
|
||||
|
@ -1040,10 +1066,10 @@ impl<'a> Parser<'a> {
|
|||
}))
|
||||
}
|
||||
|
||||
// This function parses date/time fields for both the EXTRACT function-like
|
||||
// operator and interval qualifiers. EXTRACT supports a wider set of
|
||||
// date/time fields than interval qualifiers, so this function may need to
|
||||
// be split in two.
|
||||
// This function parses date/time fields for the EXTRACT function-like
|
||||
// operator, interval qualifiers, and the ceil/floor operations.
|
||||
// EXTRACT supports a wider set of date/time fields than interval qualifiers,
|
||||
// so this function may need to be split in two.
|
||||
pub fn parse_date_time_field(&mut self) -> Result<DateTimeField, ParserError> {
|
||||
match self.next_token() {
|
||||
Token::Word(w) => match w.keyword {
|
||||
|
@ -1062,9 +1088,11 @@ impl<'a> Parser<'a> {
|
|||
Keyword::ISODOW => Ok(DateTimeField::Isodow),
|
||||
Keyword::ISOYEAR => Ok(DateTimeField::Isoyear),
|
||||
Keyword::JULIAN => Ok(DateTimeField::Julian),
|
||||
Keyword::MICROSECOND => Ok(DateTimeField::Microsecond),
|
||||
Keyword::MICROSECONDS => Ok(DateTimeField::Microseconds),
|
||||
Keyword::MILLENIUM => Ok(DateTimeField::Millenium),
|
||||
Keyword::MILLENNIUM => Ok(DateTimeField::Millennium),
|
||||
Keyword::MILLISECOND => Ok(DateTimeField::Millisecond),
|
||||
Keyword::MILLISECONDS => Ok(DateTimeField::Milliseconds),
|
||||
Keyword::QUARTER => Ok(DateTimeField::Quarter),
|
||||
Keyword::TIMEZONE => Ok(DateTimeField::Timezone),
|
||||
|
@ -1142,9 +1170,11 @@ impl<'a> Parser<'a> {
|
|||
Keyword::ISODOW,
|
||||
Keyword::ISOYEAR,
|
||||
Keyword::JULIAN,
|
||||
Keyword::MICROSECOND,
|
||||
Keyword::MICROSECONDS,
|
||||
Keyword::MILLENIUM,
|
||||
Keyword::MILLENNIUM,
|
||||
Keyword::MILLISECOND,
|
||||
Keyword::MILLISECONDS,
|
||||
Keyword::QUARTER,
|
||||
Keyword::TIMEZONE,
|
||||
|
|
|
@ -1765,18 +1765,84 @@ fn parse_extract() {
|
|||
verified_stmt("SELECT EXTRACT(ISODOW FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(ISOYEAR FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(JULIAN FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(MICROSECOND FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(MICROSECONDS FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(MILLENIUM FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(MILLENNIUM FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(MILLISECOND FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(MILLISECONDS FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(QUARTER FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(TIMEZONE FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(TIMEZONE_HOUR FROM d)");
|
||||
verified_stmt("SELECT EXTRACT(TIMEZONE_MINUTE FROM d)");
|
||||
|
||||
let res = parse_sql_statements("SELECT EXTRACT(MILLISECOND FROM d)");
|
||||
let res = parse_sql_statements("SELECT EXTRACT(JIFFY FROM d)");
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected date/time field, found: MILLISECOND".to_string()),
|
||||
ParserError::ParserError("Expected date/time field, found: JIFFY".to_string()),
|
||||
res.unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ceil_number() {
|
||||
verified_stmt("SELECT CEIL(1.5)");
|
||||
verified_stmt("SELECT CEIL(float_column) FROM my_table");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_floor_number() {
|
||||
verified_stmt("SELECT FLOOR(1.5)");
|
||||
verified_stmt("SELECT FLOOR(float_column) FROM my_table");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ceil_datetime() {
|
||||
let sql = "SELECT CEIL(d TO DAY)";
|
||||
let select = verified_only_select(sql);
|
||||
assert_eq!(
|
||||
&Expr::Ceil {
|
||||
expr: Box::new(Expr::Identifier(Ident::new("d"))),
|
||||
field: DateTimeField::Day,
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
||||
one_statement_parses_to("SELECT CEIL(d to day)", "SELECT CEIL(d TO DAY)");
|
||||
|
||||
verified_stmt("SELECT CEIL(d TO HOUR) FROM df");
|
||||
verified_stmt("SELECT CEIL(d TO MINUTE) FROM df");
|
||||
verified_stmt("SELECT CEIL(d TO SECOND) FROM df");
|
||||
verified_stmt("SELECT CEIL(d TO MILLISECOND) FROM df");
|
||||
|
||||
let res = parse_sql_statements("SELECT CEIL(d TO JIFFY) FROM df");
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected date/time field, found: JIFFY".to_string()),
|
||||
res.unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_floor_datetime() {
|
||||
let sql = "SELECT FLOOR(d TO DAY)";
|
||||
let select = verified_only_select(sql);
|
||||
assert_eq!(
|
||||
&Expr::Floor {
|
||||
expr: Box::new(Expr::Identifier(Ident::new("d"))),
|
||||
field: DateTimeField::Day,
|
||||
},
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
|
||||
one_statement_parses_to("SELECT FLOOR(d to day)", "SELECT FLOOR(d TO DAY)");
|
||||
|
||||
verified_stmt("SELECT FLOOR(d TO HOUR) FROM df");
|
||||
verified_stmt("SELECT FLOOR(d TO MINUTE) FROM df");
|
||||
verified_stmt("SELECT FLOOR(d TO SECOND) FROM df");
|
||||
verified_stmt("SELECT FLOOR(d TO MILLISECOND) FROM df");
|
||||
|
||||
let res = parse_sql_statements("SELECT FLOOR(d TO JIFFY) FROM df");
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected date/time field, found: JIFFY".to_string()),
|
||||
res.unwrap_err()
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue