Skip to content

Commit d484756

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 2f4cd8f commit d484756

File tree

3 files changed

+85
-0
lines changed

3 files changed

+85
-0
lines changed

src/sqlast/mod.rs

+30
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ pub enum ASTNode {
100100
expr: Box<ASTNode>,
101101
data_type: SQLType,
102102
},
103+
SQLExtract {
104+
field: SQLDateTimeField,
105+
expr: Box<ASTNode>,
106+
},
103107
/// `expr COLLATE collation`
104108
SQLCollate {
105109
expr: Box<ASTNode>,
@@ -186,6 +190,9 @@ impl ToString for ASTNode {
186190
expr.as_ref().to_string(),
187191
data_type.to_string()
188192
),
193+
ASTNode::SQLExtract { field, expr } => {
194+
format!("EXTRACT({} FROM {})", field.to_string(), expr.to_string())
195+
}
189196
ASTNode::SQLCollate { expr, collation } => format!(
190197
"{} COLLATE {}",
191198
expr.as_ref().to_string(),
@@ -620,6 +627,29 @@ impl ToString for SQLFunction {
620627
}
621628
}
622629

630+
#[derive(Debug, Clone, PartialEq, Hash)]
631+
pub enum SQLDateTimeField {
632+
Year,
633+
Month,
634+
Day,
635+
Hour,
636+
Minute,
637+
Second,
638+
}
639+
640+
impl ToString for SQLDateTimeField {
641+
fn to_string(&self) -> String {
642+
match self {
643+
SQLDateTimeField::Year => "YEAR".to_string(),
644+
SQLDateTimeField::Month => "MONTH".to_string(),
645+
SQLDateTimeField::Day => "DAY".to_string(),
646+
SQLDateTimeField::Hour => "HOUR".to_string(),
647+
SQLDateTimeField::Minute => "MINUTE".to_string(),
648+
SQLDateTimeField::Second => "SECOND".to_string(),
649+
}
650+
}
651+
}
652+
623653
/// External table's available file format
624654
#[derive(Debug, Clone, PartialEq, Hash)]
625655
pub enum FileFormat {

src/sqlparser.rs

+26
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ impl Parser {
193193
"CASE" => self.parse_case_expression(),
194194
"CAST" => self.parse_cast_expression(),
195195
"EXISTS" => self.parse_exists_expression(),
196+
"EXTRACT" => self.parse_extract_expression(),
196197
"NOT" => Ok(ASTNode::SQLUnary {
197198
operator: SQLOperator::Not,
198199
expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?),
@@ -417,6 +418,31 @@ impl Parser {
417418
Ok(exists_node)
418419
}
419420

421+
pub fn parse_extract_expression(&mut self) -> Result<ASTNode, ParserError> {
422+
self.expect_token(&Token::LParen)?;
423+
let tok = self.next_token();
424+
let field = if let Some(Token::SQLWord(ref k)) = tok {
425+
match k.keyword.as_ref() {
426+
"YEAR" => SQLDateTimeField::Year,
427+
"MONTH" => SQLDateTimeField::Month,
428+
"DAY" => SQLDateTimeField::Day,
429+
"HOUR" => SQLDateTimeField::Hour,
430+
"MINUTE" => SQLDateTimeField::Minute,
431+
"SECOND" => SQLDateTimeField::Second,
432+
_ => self.expected("Date/time field inside of EXTRACT function", tok)?,
433+
}
434+
} else {
435+
self.expected("Date/time field inside of EXTRACT function", tok)?
436+
};
437+
self.expect_keyword("FROM")?;
438+
let expr = self.parse_expr()?;
439+
self.expect_token(&Token::RParen)?;
440+
Ok(ASTNode::SQLExtract {
441+
field,
442+
expr: Box::new(expr),
443+
})
444+
}
445+
420446
/// Parse an operator following an expression
421447
pub fn parse_infix(&mut self, expr: ASTNode, precedence: u8) -> Result<ASTNode, ParserError> {
422448
debug!("parsing infix");

tests/sqlparser_common.rs

+29
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,35 @@ fn parse_cast() {
797797
);
798798
}
799799

800+
#[test]
801+
fn parse_extract() {
802+
let sql = "SELECT EXTRACT(YEAR FROM d)";
803+
let select = verified_only_select(sql);
804+
assert_eq!(
805+
&ASTNode::SQLExtract {
806+
field: SQLDateTimeField::Year,
807+
expr: Box::new(ASTNode::SQLIdentifier("d".to_string())),
808+
},
809+
expr_from_projection(only(&select.projection)),
810+
);
811+
812+
one_statement_parses_to("SELECT EXTRACT(year from d)", "SELECT EXTRACT(YEAR FROM d)");
813+
814+
verified_stmt("SELECT EXTRACT(MONTH FROM d)");
815+
verified_stmt("SELECT EXTRACT(DAY FROM d)");
816+
verified_stmt("SELECT EXTRACT(HOUR FROM d)");
817+
verified_stmt("SELECT EXTRACT(MINUTE FROM d)");
818+
verified_stmt("SELECT EXTRACT(SECOND FROM d)");
819+
820+
let res = parse_sql_statements("SELECT EXTRACT(MILLISECOND FROM d)");
821+
assert_eq!(
822+
ParserError::ParserError(
823+
"Expected Date/time field inside of EXTRACT function, found: MILLISECOND".to_string()
824+
),
825+
res.unwrap_err()
826+
);
827+
}
828+
800829
#[test]
801830
fn parse_create_table() {
802831
let sql = "CREATE TABLE uk_cities (\

0 commit comments

Comments
 (0)