Skip to content

Commit 6adad4f

Browse files
committed
Support EXTRACT function-like operator
The EXTRACT function, for extracting components of a date from a timestamp, has special syntax: `EXTRACT(<field> FROM <timestamp>)`.
1 parent 2308c1c commit 6adad4f

File tree

3 files changed

+85
-0
lines changed

3 files changed

+85
-0
lines changed

src/sqlast/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ pub enum ASTNode {
9393
expr: Box<ASTNode>,
9494
data_type: SQLType,
9595
},
96+
SQLExtract {
97+
field: SQLDateTimeField,
98+
expr: Box<ASTNode>,
99+
},
96100
/// `expr COLLATE collation`
97101
SQLCollate {
98102
expr: Box<ASTNode>,
@@ -182,6 +186,9 @@ impl ToString for ASTNode {
182186
expr.as_ref().to_string(),
183187
data_type.to_string()
184188
),
189+
ASTNode::SQLExtract { field, expr } => {
190+
format!("EXTRACT({} FROM {})", field.to_string(), expr.to_string())
191+
}
185192
ASTNode::SQLCollate { expr, collation } => format!(
186193
"{} COLLATE {}",
187194
expr.as_ref().to_string(),
@@ -600,6 +607,29 @@ impl ToString for SQLColumnDef {
600607
}
601608
}
602609

610+
#[derive(Debug, Clone, PartialEq, Hash)]
611+
pub enum SQLDateTimeField {
612+
Year,
613+
Month,
614+
Day,
615+
Hour,
616+
Minute,
617+
Second,
618+
}
619+
620+
impl ToString for SQLDateTimeField {
621+
fn to_string(&self) -> String {
622+
match self {
623+
SQLDateTimeField::Year => "YEAR".to_string(),
624+
SQLDateTimeField::Month => "MONTH".to_string(),
625+
SQLDateTimeField::Day => "DAY".to_string(),
626+
SQLDateTimeField::Hour => "HOUR".to_string(),
627+
SQLDateTimeField::Minute => "MINUTE".to_string(),
628+
SQLDateTimeField::Second => "SECOND".to_string(),
629+
}
630+
}
631+
}
632+
603633
/// External table's available file format
604634
#[derive(Debug, Clone, PartialEq)]
605635
pub enum FileFormat {

src/sqlparser.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ impl Parser {
191191
}
192192
"CASE" => self.parse_case_expression(),
193193
"CAST" => self.parse_cast_expression(),
194+
"EXTRACT" => self.parse_extract_expression(),
194195
"NOT" => Ok(ASTNode::SQLUnary {
195196
operator: SQLOperator::Not,
196197
expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?),
@@ -404,6 +405,31 @@ impl Parser {
404405
})
405406
}
406407

408+
pub fn parse_extract_expression(&mut self) -> Result<ASTNode, ParserError> {
409+
self.expect_token(&Token::LParen)?;
410+
let tok = self.next_token();
411+
let field = if let Some(Token::SQLWord(ref k)) = tok {
412+
match k.keyword.as_ref() {
413+
"YEAR" => SQLDateTimeField::Year,
414+
"MONTH" => SQLDateTimeField::Month,
415+
"DAY" => SQLDateTimeField::Day,
416+
"HOUR" => SQLDateTimeField::Hour,
417+
"MINUTE" => SQLDateTimeField::Minute,
418+
"SECOND" => SQLDateTimeField::Second,
419+
_ => self.expected("Date/time field inside of EXTRACT function", tok)?,
420+
}
421+
} else {
422+
self.expected("Date/time field inside of EXTRACT function", tok)?
423+
};
424+
self.expect_keyword("FROM")?;
425+
let expr = self.parse_expr()?;
426+
self.expect_token(&Token::RParen)?;
427+
Ok(ASTNode::SQLExtract {
428+
field,
429+
expr: Box::new(expr),
430+
})
431+
}
432+
407433
/// Parse an operator following an expression
408434
pub fn parse_infix(&mut self, expr: ASTNode, precedence: u8) -> Result<ASTNode, ParserError> {
409435
debug!("parsing infix");

tests/sqlparser_common.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,35 @@ fn parse_cast() {
718718
);
719719
}
720720

721+
#[test]
722+
fn parse_extract() {
723+
let sql = "SELECT EXTRACT(YEAR FROM d)";
724+
let select = verified_only_select(sql);
725+
assert_eq!(
726+
&ASTNode::SQLExtract {
727+
field: SQLDateTimeField::Year,
728+
expr: Box::new(ASTNode::SQLIdentifier("d".to_string())),
729+
},
730+
expr_from_projection(only(&select.projection)),
731+
);
732+
733+
one_statement_parses_to("SELECT EXTRACT(year from d)", "SELECT EXTRACT(YEAR FROM d)");
734+
735+
verified_stmt("SELECT EXTRACT(MONTH FROM d)");
736+
verified_stmt("SELECT EXTRACT(DAY FROM d)");
737+
verified_stmt("SELECT EXTRACT(HOUR FROM d)");
738+
verified_stmt("SELECT EXTRACT(MINUTE FROM d)");
739+
verified_stmt("SELECT EXTRACT(SECOND FROM d)");
740+
741+
let res = parse_sql_statements("SELECT EXTRACT(MILLISECOND FROM d)");
742+
assert_eq!(
743+
ParserError::ParserError(
744+
"Expected Date/time field inside of EXTRACT function, found: MILLISECOND".to_string()
745+
),
746+
res.unwrap_err()
747+
);
748+
}
749+
721750
#[test]
722751
fn parse_create_table() {
723752
let sql = "CREATE TABLE uk_cities (\

0 commit comments

Comments
 (0)