diff --git a/src/sqlast/mod.rs b/src/sqlast/mod.rs index 9431cce16..f109888e4 100644 --- a/src/sqlast/mod.rs +++ b/src/sqlast/mod.rs @@ -28,7 +28,7 @@ pub use self::query::{ SQLSelectItem, SQLSetExpr, SQLSetOperator, SQLValues, TableAlias, TableFactor, }; pub use self::sqltype::SQLType; -pub use self::value::Value; +pub use self::value::{SQLDateTimeField, Value}; pub use self::sql_operator::SQLOperator; @@ -635,29 +635,6 @@ impl ToString for SQLFunction { } } -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum SQLDateTimeField { - Year, - Month, - Day, - Hour, - Minute, - Second, -} - -impl ToString for SQLDateTimeField { - fn to_string(&self) -> String { - match self { - SQLDateTimeField::Year => "YEAR".to_string(), - SQLDateTimeField::Month => "MONTH".to_string(), - SQLDateTimeField::Day => "DAY".to_string(), - SQLDateTimeField::Hour => "HOUR".to_string(), - SQLDateTimeField::Minute => "MINUTE".to_string(), - SQLDateTimeField::Second => "SECOND".to_string(), - } - } -} - /// External table's available file format #[derive(Debug, Clone, PartialEq, Hash)] pub enum FileFormat { diff --git a/src/sqlast/sqltype.rs b/src/sqlast/sqltype.rs index ba339d078..992ba5c0c 100644 --- a/src/sqlast/sqltype.rs +++ b/src/sqlast/sqltype.rs @@ -39,6 +39,8 @@ pub enum SQLType { Time, /// Timestamp Timestamp, + /// Interval + Interval, /// Regclass used in postgresql serial Regclass, /// Text @@ -78,6 +80,7 @@ impl ToString for SQLType { SQLType::Date => "date".to_string(), SQLType::Time => "time".to_string(), SQLType::Timestamp => "timestamp".to_string(), + SQLType::Interval => "interval".to_string(), SQLType::Regclass => "regclass".to_string(), SQLType::Text => "text".to_string(), SQLType::Bytea => "bytea".to_string(), diff --git a/src/sqlast/value.rs b/src/sqlast/value.rs index 21bb199d4..79e5243ba 100644 --- a/src/sqlast/value.rs +++ b/src/sqlast/value.rs @@ -21,6 +21,14 @@ pub enum Value { Time(String), /// Timestamp literals, which include both a date and time Timestamp(String), + /// INTERVAL literals, e.g. INTERVAL '12:34.56' MINUTE TO SECOND (2) + Interval { + value: String, + leading_field: SQLDateTimeField, + leading_precision: Option, + last_field: Option, + fractional_seconds_precision: Option, + }, /// NULL value in insert statements, Null, } @@ -37,11 +45,74 @@ impl ToString for Value { Value::Date(v) => format!("DATE '{}'", escape_single_quote_string(v)), Value::Time(v) => format!("TIME '{}'", escape_single_quote_string(v)), Value::Timestamp(v) => format!("TIMESTAMP '{}'", escape_single_quote_string(v)), + Value::Interval { + value, + leading_field: SQLDateTimeField::Second, + leading_precision: Some(leading_precision), + last_field, + fractional_seconds_precision: Some(fractional_seconds_precision), + } => { + // When the leading field is SECOND, the parser guarantees that + // the last field is None. + assert!(last_field.is_none()); + format!( + "INTERVAL '{}' SECOND ({}, {})", + escape_single_quote_string(value), + leading_precision, + fractional_seconds_precision + ) + } + Value::Interval { + value, + leading_field, + leading_precision, + last_field, + fractional_seconds_precision, + } => { + let mut s = format!( + "INTERVAL '{}' {}", + escape_single_quote_string(value), + leading_field.to_string() + ); + if let Some(leading_precision) = leading_precision { + s += &format!(" ({})", leading_precision); + } + if let Some(last_field) = last_field { + s += &format!(" TO {}", last_field.to_string()); + } + if let Some(fractional_seconds_precision) = fractional_seconds_precision { + s += &format!(" ({})", fractional_seconds_precision); + } + s + } Value::Null => "NULL".to_string(), } } } +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum SQLDateTimeField { + Year, + Month, + Day, + Hour, + Minute, + Second, +} + +impl ToString for SQLDateTimeField { + fn to_string(&self) -> String { + match self { + SQLDateTimeField::Year => "YEAR".to_string(), + SQLDateTimeField::Month => "MONTH".to_string(), + SQLDateTimeField::Day => "DAY".to_string(), + SQLDateTimeField::Hour => "HOUR".to_string(), + SQLDateTimeField::Minute => "MINUTE".to_string(), + SQLDateTimeField::Second => "SECOND".to_string(), + } + } +} + fn escape_single_quote_string(s: &str) -> String { let mut escaped = String::new(); for c in s.chars() { diff --git a/src/sqlparser.rs b/src/sqlparser.rs index 1b1b83b61..2f28e9628 100644 --- a/src/sqlparser.rs +++ b/src/sqlparser.rs @@ -195,6 +195,7 @@ impl Parser { "DATE" => Ok(ASTNode::SQLValue(Value::Date(self.parse_literal_string()?))), "EXISTS" => self.parse_exists_expression(), "EXTRACT" => self.parse_extract_expression(), + "INTERVAL" => self.parse_literal_interval(), "NOT" => Ok(ASTNode::SQLUnary { operator: SQLOperator::Not, expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?), @@ -425,20 +426,7 @@ impl Parser { pub fn parse_extract_expression(&mut self) -> Result { self.expect_token(&Token::LParen)?; - let tok = self.next_token(); - let field = if let Some(Token::SQLWord(ref k)) = tok { - match k.keyword.as_ref() { - "YEAR" => SQLDateTimeField::Year, - "MONTH" => SQLDateTimeField::Month, - "DAY" => SQLDateTimeField::Day, - "HOUR" => SQLDateTimeField::Hour, - "MINUTE" => SQLDateTimeField::Minute, - "SECOND" => SQLDateTimeField::Second, - _ => self.expected("Date/time field inside of EXTRACT function", tok)?, - } - } else { - self.expected("Date/time field inside of EXTRACT function", tok)? - }; + let field = self.parse_date_time_field()?; self.expect_keyword("FROM")?; let expr = self.parse_expr()?; self.expect_token(&Token::RParen)?; @@ -448,6 +436,90 @@ impl Parser { }) } + // 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. + pub fn parse_date_time_field(&mut self) -> Result { + let tok = self.next_token(); + if let Some(Token::SQLWord(ref k)) = tok { + match k.keyword.as_ref() { + "YEAR" => Ok(SQLDateTimeField::Year), + "MONTH" => Ok(SQLDateTimeField::Month), + "DAY" => Ok(SQLDateTimeField::Day), + "HOUR" => Ok(SQLDateTimeField::Hour), + "MINUTE" => Ok(SQLDateTimeField::Minute), + "SECOND" => Ok(SQLDateTimeField::Second), + _ => self.expected("date/time field", tok)?, + } + } else { + self.expected("date/time field", tok)? + } + } + + /// Parse an INTERVAL literal. + /// + /// Some syntactically valid intervals: + /// + /// 1. `INTERVAL '1' DAY` + /// 2. `INTERVAL '1-1' YEAR TO MONTH` + /// 3. `INTERVAL '1' SECOND` + /// 4. `INTERVAL '1:1:1.1' HOUR (5) TO SECOND (5)` + /// 5. `INTERVAL '1.1' SECOND (2, 2)` + /// 6. `INTERVAL '1:1' HOUR (5) TO MINUTE (5)` + /// + /// Note that we do not currently attempt to parse the quoted value. + pub fn parse_literal_interval(&mut self) -> Result { + // The SQL standard allows an optional sign before the value string, but + // it is not clear if any implementations support that syntax, so we + // don't currently try to parse it. (The sign can instead be included + // inside the value string.) + + // The first token in an interval is a string literal which specifies + // the duration of the interval. + let value = self.parse_literal_string()?; + + // Following the string literal is a qualifier which indicates the units + // of the duration specified in the string literal. + // + // Note that PostgreSQL allows omitting the qualifier, but we currently + // require at least the leading field, in accordance with the ANSI spec. + let leading_field = self.parse_date_time_field()?; + + let (leading_precision, last_field, fsec_precision) = + if leading_field == SQLDateTimeField::Second { + // SQL mandates special syntax for `SECOND TO SECOND` literals. + // Instead of + // `SECOND [()] TO SECOND[()]` + // one must use the special format: + // `SECOND [( [ , ] )]` + let last_field = None; + let (leading_precision, fsec_precision) = self.parse_optional_precision_scale()?; + (leading_precision, last_field, fsec_precision) + } else { + let leading_precision = self.parse_optional_precision()?; + if self.parse_keyword("TO") { + let last_field = Some(self.parse_date_time_field()?); + let fsec_precision = if last_field == Some(SQLDateTimeField::Second) { + self.parse_optional_precision()? + } else { + None + }; + (leading_precision, last_field, fsec_precision) + } else { + (leading_precision, None, None) + } + }; + + Ok(ASTNode::SQLValue(Value::Interval { + value, + leading_field, + leading_precision, + last_field, + fractional_seconds_precision: fsec_precision, + })) + } + /// Parse an operator following an expression pub fn parse_infix(&mut self, expr: ASTNode, precedence: u8) -> Result { debug!("parsing infix"); @@ -1182,6 +1254,10 @@ impl Parser { } Ok(SQLType::Time) } + // Interval types can be followed by a complicated interval + // qualifier that we don't currently support. See + // parse_interval_literal for a taste. + "INTERVAL" => Ok(SQLType::Interval), "REGCLASS" => Ok(SQLType::Regclass), "TEXT" => { if self.consume_token(&Token::LBracket) { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 1c051ca00..110faca0f 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -819,9 +819,7 @@ fn parse_extract() { let res = parse_sql_statements("SELECT EXTRACT(MILLISECOND FROM d)"); assert_eq!( - ParserError::ParserError( - "Expected Date/time field inside of EXTRACT function, found: MILLISECOND".to_string() - ), + ParserError::ParserError("Expected date/time field, found: MILLISECOND".to_string()), res.unwrap_err() ); } @@ -1115,6 +1113,100 @@ fn parse_literal_timestamp() { ); } +#[test] +fn parse_literal_interval() { + let sql = "SELECT INTERVAL '1-1' YEAR TO MONTH"; + let select = verified_only_select(sql); + assert_eq!( + &ASTNode::SQLValue(Value::Interval { + value: "1-1".into(), + leading_field: SQLDateTimeField::Year, + leading_precision: None, + last_field: Some(SQLDateTimeField::Month), + fractional_seconds_precision: None, + }), + expr_from_projection(only(&select.projection)), + ); + + let sql = "SELECT INTERVAL '01:01.01' MINUTE (5) TO SECOND (5)"; + let select = verified_only_select(sql); + assert_eq!( + &ASTNode::SQLValue(Value::Interval { + value: "01:01.01".into(), + leading_field: SQLDateTimeField::Minute, + leading_precision: Some(5), + last_field: Some(SQLDateTimeField::Second), + fractional_seconds_precision: Some(5), + }), + expr_from_projection(only(&select.projection)), + ); + + let sql = "SELECT INTERVAL '1' SECOND (5, 4)"; + let select = verified_only_select(sql); + assert_eq!( + &ASTNode::SQLValue(Value::Interval { + value: "1".into(), + leading_field: SQLDateTimeField::Second, + leading_precision: Some(5), + last_field: None, + fractional_seconds_precision: Some(4), + }), + expr_from_projection(only(&select.projection)), + ); + + let sql = "SELECT INTERVAL '10' HOUR"; + let select = verified_only_select(sql); + assert_eq!( + &ASTNode::SQLValue(Value::Interval { + value: "10".into(), + leading_field: SQLDateTimeField::Hour, + leading_precision: None, + last_field: None, + fractional_seconds_precision: None, + }), + expr_from_projection(only(&select.projection)), + ); + + let sql = "SELECT INTERVAL '10' HOUR (1)"; + let select = verified_only_select(sql); + assert_eq!( + &ASTNode::SQLValue(Value::Interval { + value: "10".into(), + leading_field: SQLDateTimeField::Hour, + leading_precision: Some(1), + last_field: None, + fractional_seconds_precision: None, + }), + expr_from_projection(only(&select.projection)), + ); + + let result = parse_sql_statements("SELECT INTERVAL '1' SECOND TO SECOND"); + assert_eq!( + ParserError::ParserError("Expected end of statement, found: SECOND".to_string()), + result.unwrap_err(), + ); + + let result = parse_sql_statements("SELECT INTERVAL '10' HOUR (1) TO HOUR (2)"); + assert_eq!( + ParserError::ParserError("Expected end of statement, found: (".to_string()), + result.unwrap_err(), + ); + + verified_only_select("SELECT INTERVAL '1' YEAR"); + verified_only_select("SELECT INTERVAL '1' MONTH"); + verified_only_select("SELECT INTERVAL '1' DAY"); + verified_only_select("SELECT INTERVAL '1' HOUR"); + verified_only_select("SELECT INTERVAL '1' MINUTE"); + verified_only_select("SELECT INTERVAL '1' SECOND"); + verified_only_select("SELECT INTERVAL '1' YEAR TO MONTH"); + verified_only_select("SELECT INTERVAL '1' DAY TO HOUR"); + verified_only_select("SELECT INTERVAL '1' DAY TO MINUTE"); + verified_only_select("SELECT INTERVAL '1' DAY TO SECOND"); + verified_only_select("SELECT INTERVAL '1' HOUR TO MINUTE"); + verified_only_select("SELECT INTERVAL '1' HOUR TO SECOND"); + verified_only_select("SELECT INTERVAL '1' MINUTE TO SECOND"); +} + #[test] fn parse_simple_math_expr_plus() { let sql = "SELECT a + b, 2 + a, 2.5 + a, a_f + b_f, 2 + a_f, 2.5 + a_f FROM c";