mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-04 21:20:32 +00:00
Support interval literals
This commit is contained in:
parent
1f87083906
commit
2798ddf5fd
5 changed files with 260 additions and 41 deletions
|
@ -28,7 +28,7 @@ pub use self::query::{
|
||||||
SQLSelectItem, SQLSetExpr, SQLSetOperator, SQLValues, TableAlias, TableFactor,
|
SQLSelectItem, SQLSetExpr, SQLSetOperator, SQLValues, TableAlias, TableFactor,
|
||||||
};
|
};
|
||||||
pub use self::sqltype::SQLType;
|
pub use self::sqltype::SQLType;
|
||||||
pub use self::value::Value;
|
pub use self::value::{SQLDateTimeField, Value};
|
||||||
|
|
||||||
pub use self::sql_operator::SQLOperator;
|
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
|
/// External table's available file format
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
pub enum FileFormat {
|
pub enum FileFormat {
|
||||||
|
|
|
@ -39,6 +39,8 @@ pub enum SQLType {
|
||||||
Time,
|
Time,
|
||||||
/// Timestamp
|
/// Timestamp
|
||||||
Timestamp,
|
Timestamp,
|
||||||
|
/// Interval
|
||||||
|
Interval,
|
||||||
/// Regclass used in postgresql serial
|
/// Regclass used in postgresql serial
|
||||||
Regclass,
|
Regclass,
|
||||||
/// Text
|
/// Text
|
||||||
|
@ -78,6 +80,7 @@ impl ToString for SQLType {
|
||||||
SQLType::Date => "date".to_string(),
|
SQLType::Date => "date".to_string(),
|
||||||
SQLType::Time => "time".to_string(),
|
SQLType::Time => "time".to_string(),
|
||||||
SQLType::Timestamp => "timestamp".to_string(),
|
SQLType::Timestamp => "timestamp".to_string(),
|
||||||
|
SQLType::Interval => "interval".to_string(),
|
||||||
SQLType::Regclass => "regclass".to_string(),
|
SQLType::Regclass => "regclass".to_string(),
|
||||||
SQLType::Text => "text".to_string(),
|
SQLType::Text => "text".to_string(),
|
||||||
SQLType::Bytea => "bytea".to_string(),
|
SQLType::Bytea => "bytea".to_string(),
|
||||||
|
|
|
@ -21,6 +21,14 @@ pub enum Value {
|
||||||
Time(String),
|
Time(String),
|
||||||
/// Timestamp literals, which include both a date and time
|
/// Timestamp literals, which include both a date and time
|
||||||
Timestamp(String),
|
Timestamp(String),
|
||||||
|
/// INTERVAL literals, e.g. INTERVAL '12:34.56' MINUTE TO SECOND (2)
|
||||||
|
Interval {
|
||||||
|
value: String,
|
||||||
|
leading_field: SQLDateTimeField,
|
||||||
|
leading_precision: Option<u64>,
|
||||||
|
last_field: Option<SQLDateTimeField>,
|
||||||
|
fractional_seconds_precision: Option<u64>,
|
||||||
|
},
|
||||||
/// NULL value in insert statements,
|
/// NULL value in insert statements,
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
@ -37,11 +45,74 @@ impl ToString for Value {
|
||||||
Value::Date(v) => format!("DATE '{}'", escape_single_quote_string(v)),
|
Value::Date(v) => format!("DATE '{}'", escape_single_quote_string(v)),
|
||||||
Value::Time(v) => format!("TIME '{}'", 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::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(),
|
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 {
|
fn escape_single_quote_string(s: &str) -> String {
|
||||||
let mut escaped = String::new();
|
let mut escaped = String::new();
|
||||||
for c in s.chars() {
|
for c in s.chars() {
|
||||||
|
|
104
src/sqlparser.rs
104
src/sqlparser.rs
|
@ -195,6 +195,7 @@ impl Parser {
|
||||||
"DATE" => Ok(ASTNode::SQLValue(Value::Date(self.parse_literal_string()?))),
|
"DATE" => Ok(ASTNode::SQLValue(Value::Date(self.parse_literal_string()?))),
|
||||||
"EXISTS" => self.parse_exists_expression(),
|
"EXISTS" => self.parse_exists_expression(),
|
||||||
"EXTRACT" => self.parse_extract_expression(),
|
"EXTRACT" => self.parse_extract_expression(),
|
||||||
|
"INTERVAL" => self.parse_literal_interval(),
|
||||||
"NOT" => Ok(ASTNode::SQLUnary {
|
"NOT" => Ok(ASTNode::SQLUnary {
|
||||||
operator: SQLOperator::Not,
|
operator: SQLOperator::Not,
|
||||||
expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?),
|
expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?),
|
||||||
|
@ -425,20 +426,7 @@ impl Parser {
|
||||||
|
|
||||||
pub fn parse_extract_expression(&mut self) -> Result<ASTNode, ParserError> {
|
pub fn parse_extract_expression(&mut self) -> Result<ASTNode, ParserError> {
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
let tok = self.next_token();
|
let field = self.parse_date_time_field()?;
|
||||||
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)?
|
|
||||||
};
|
|
||||||
self.expect_keyword("FROM")?;
|
self.expect_keyword("FROM")?;
|
||||||
let expr = self.parse_expr()?;
|
let expr = self.parse_expr()?;
|
||||||
self.expect_token(&Token::RParen)?;
|
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<SQLDateTimeField, ParserError> {
|
||||||
|
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<ASTNode, ParserError> {
|
||||||
|
// 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 [(<leading precision>)] TO SECOND[(<fractional seconds precision>)]`
|
||||||
|
// one must use the special format:
|
||||||
|
// `SECOND [( <leading precision> [ , <fractional seconds precision>] )]`
|
||||||
|
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
|
/// Parse an operator following an expression
|
||||||
pub fn parse_infix(&mut self, expr: ASTNode, precedence: u8) -> Result<ASTNode, ParserError> {
|
pub fn parse_infix(&mut self, expr: ASTNode, precedence: u8) -> Result<ASTNode, ParserError> {
|
||||||
debug!("parsing infix");
|
debug!("parsing infix");
|
||||||
|
@ -1182,6 +1254,10 @@ impl Parser {
|
||||||
}
|
}
|
||||||
Ok(SQLType::Time)
|
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),
|
"REGCLASS" => Ok(SQLType::Regclass),
|
||||||
"TEXT" => {
|
"TEXT" => {
|
||||||
if self.consume_token(&Token::LBracket) {
|
if self.consume_token(&Token::LBracket) {
|
||||||
|
|
|
@ -819,9 +819,7 @@ fn parse_extract() {
|
||||||
|
|
||||||
let res = parse_sql_statements("SELECT EXTRACT(MILLISECOND FROM d)");
|
let res = parse_sql_statements("SELECT EXTRACT(MILLISECOND FROM d)");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ParserError::ParserError(
|
ParserError::ParserError("Expected date/time field, found: MILLISECOND".to_string()),
|
||||||
"Expected Date/time field inside of EXTRACT function, found: MILLISECOND".to_string()
|
|
||||||
),
|
|
||||||
res.unwrap_err()
|
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]
|
#[test]
|
||||||
fn parse_simple_math_expr_plus() {
|
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";
|
let sql = "SELECT a + b, 2 + a, 2.5 + a, a_f + b_f, 2 + a_f, 2.5 + a_f FROM c";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue