Skip to content

Commit 8ea702b

Browse files
committed
Merge branch 'extract' into dates
2 parents 679c4d3 + 6adad4f commit 8ea702b

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>,
@@ -185,6 +189,9 @@ impl ToString for ASTNode {
185189
expr.as_ref().to_string(),
186190
data_type.to_string()
187191
),
192+
ASTNode::SQLExtract { field, expr } => {
193+
format!("EXTRACT({} FROM {})", field.to_string(), expr.to_string())
194+
}
188195
ASTNode::SQLCollate { expr, collation } => format!(
189196
"{} COLLATE {}",
190197
expr.as_ref().to_string(),
@@ -604,6 +611,29 @@ impl ToString for SQLColumnDef {
604611
}
605612
}
606613

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

src/sqlparser.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ impl Parser {
192192
"CASE" => self.parse_case_expression(),
193193
"CAST" => self.parse_cast_expression(),
194194
"EXISTS" => self.parse_exists_expression(),
195+
"EXTRACT" => self.parse_extract_expression(),
195196
"NOT" => Ok(ASTNode::SQLUnary {
196197
operator: SQLOperator::Not,
197198
expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?),
@@ -413,6 +414,31 @@ impl Parser {
413414
Ok(exists_node)
414415
}
415416

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