Skip to content

Commit bd93f00

Browse files
samuelcolvinayman-sigma
authored andcommitted
allow DateTimeField::Custom with EXTRACT in Postgres (apache#1394)
1 parent 2515cb9 commit bd93f00

File tree

6 files changed

+111
-6
lines changed

6 files changed

+111
-6
lines changed

src/dialect/generic.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,12 @@ impl Dialect for GenericDialect {
7878
fn support_map_literal_syntax(&self) -> bool {
7979
true
8080
}
81+
82+
fn allow_extract_custom(&self) -> bool {
83+
true
84+
}
85+
86+
fn allow_extract_single_quotes(&self) -> bool {
87+
true
88+
}
8189
}

src/dialect/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,16 @@ pub trait Dialect: Debug + Any {
499499
fn describe_requires_table_keyword(&self) -> bool {
500500
false
501501
}
502+
503+
/// Returns true if this dialect allows the `EXTRACT` function to words other than [`Keyword`].
504+
fn allow_extract_custom(&self) -> bool {
505+
false
506+
}
507+
508+
/// Returns true if this dialect allows the `EXTRACT` function to use single quotes in the part being extracted.
509+
fn allow_extract_single_quotes(&self) -> bool {
510+
false
511+
}
502512
}
503513

504514
/// This represents the operators for which precedence must be defined

src/dialect/postgresql.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,14 @@ impl Dialect for PostgreSqlDialect {
154154
Precedence::Or => OR_PREC,
155155
}
156156
}
157+
158+
fn allow_extract_custom(&self) -> bool {
159+
true
160+
}
161+
162+
fn allow_extract_single_quotes(&self) -> bool {
163+
true
164+
}
157165
}
158166

159167
pub fn parse_comment(parser: &mut Parser) -> Result<Statement, ParserError> {

src/dialect/snowflake.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,14 @@ impl Dialect for SnowflakeDialect {
158158
fn describe_requires_table_keyword(&self) -> bool {
159159
true
160160
}
161+
162+
fn allow_extract_custom(&self) -> bool {
163+
true
164+
}
165+
166+
fn allow_extract_single_quotes(&self) -> bool {
167+
true
168+
}
161169
}
162170

163171
/// Parse snowflake create table statement.

src/parser/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,15 +1974,14 @@ impl<'a> Parser<'a> {
19741974
Keyword::TIMEZONE_HOUR => Ok(DateTimeField::TimezoneHour),
19751975
Keyword::TIMEZONE_MINUTE => Ok(DateTimeField::TimezoneMinute),
19761976
Keyword::TIMEZONE_REGION => Ok(DateTimeField::TimezoneRegion),
1977-
_ if dialect_of!(self is SnowflakeDialect | GenericDialect) => {
1977+
_ if self.dialect.allow_extract_custom() => {
19781978
self.prev_token();
19791979
let custom = self.parse_identifier(false)?;
19801980
Ok(DateTimeField::Custom(custom))
19811981
}
19821982
_ => self.expected("date/time field", next_token),
19831983
},
1984-
Token::SingleQuotedString(_) if dialect_of!(self is SnowflakeDialect | GenericDialect) =>
1985-
{
1984+
Token::SingleQuotedString(_) if self.dialect.allow_extract_single_quotes() => {
19861985
self.prev_token();
19871986
let custom = self.parse_identifier(false)?;
19881987
Ok(DateTimeField::Custom(custom))

tests/sqlparser_common.rs

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,7 +2475,7 @@ fn parse_extract() {
24752475
verified_stmt("SELECT EXTRACT(TIMEZONE_REGION FROM d)");
24762476
verified_stmt("SELECT EXTRACT(TIME FROM d)");
24772477

2478-
let dialects = all_dialects_except(|d| d.is::<SnowflakeDialect>() || d.is::<GenericDialect>());
2478+
let dialects = all_dialects_except(|d| d.allow_extract_custom());
24792479
let res = dialects.parse_sql_statements("SELECT EXTRACT(JIFFY FROM d)");
24802480
assert_eq!(
24812481
ParserError::ParserError("Expected: date/time field, found: JIFFY".to_string()),
@@ -2574,7 +2574,7 @@ fn parse_ceil_datetime() {
25742574
verified_stmt("SELECT CEIL(d TO SECOND) FROM df");
25752575
verified_stmt("SELECT CEIL(d TO MILLISECOND) FROM df");
25762576

2577-
let dialects = all_dialects_except(|d| d.is::<SnowflakeDialect>() || d.is::<GenericDialect>());
2577+
let dialects = all_dialects_except(|d| d.allow_extract_custom());
25782578
let res = dialects.parse_sql_statements("SELECT CEIL(d TO JIFFY) FROM df");
25792579
assert_eq!(
25802580
ParserError::ParserError("Expected: date/time field, found: JIFFY".to_string()),
@@ -2601,7 +2601,7 @@ fn parse_floor_datetime() {
26012601
verified_stmt("SELECT FLOOR(d TO SECOND) FROM df");
26022602
verified_stmt("SELECT FLOOR(d TO MILLISECOND) FROM df");
26032603

2604-
let dialects = all_dialects_except(|d| d.is::<SnowflakeDialect>() || d.is::<GenericDialect>());
2604+
let dialects = all_dialects_except(|d| d.allow_extract_custom());
26052605
let res = dialects.parse_sql_statements("SELECT FLOOR(d TO JIFFY) FROM df");
26062606
assert_eq!(
26072607
ParserError::ParserError("Expected: date/time field, found: JIFFY".to_string()),
@@ -10469,3 +10469,75 @@ fn test_group_by_nothing() {
1046910469
);
1047010470
}
1047110471
}
10472+
10473+
#[test]
10474+
fn test_extract_seconds_ok() {
10475+
let dialects = all_dialects_where(|d| d.allow_extract_custom());
10476+
let stmt = dialects.verified_expr("EXTRACT(seconds FROM '2 seconds'::INTERVAL)");
10477+
10478+
assert_eq!(
10479+
stmt,
10480+
Expr::Extract {
10481+
field: DateTimeField::Custom(Ident {
10482+
value: "seconds".to_string(),
10483+
quote_style: None,
10484+
}),
10485+
syntax: ExtractSyntax::From,
10486+
expr: Box::new(Expr::Cast {
10487+
kind: CastKind::DoubleColon,
10488+
expr: Box::new(Expr::Value(Value::SingleQuotedString(
10489+
"2 seconds".to_string()
10490+
))),
10491+
data_type: DataType::Interval,
10492+
format: None,
10493+
}),
10494+
}
10495+
)
10496+
}
10497+
10498+
#[test]
10499+
fn test_extract_seconds_single_quote_ok() {
10500+
let dialects = all_dialects_where(|d| d.allow_extract_custom());
10501+
let stmt = dialects.verified_expr(r#"EXTRACT('seconds' FROM '2 seconds'::INTERVAL)"#);
10502+
10503+
assert_eq!(
10504+
stmt,
10505+
Expr::Extract {
10506+
field: DateTimeField::Custom(Ident {
10507+
value: "seconds".to_string(),
10508+
quote_style: Some('\''),
10509+
}),
10510+
syntax: ExtractSyntax::From,
10511+
expr: Box::new(Expr::Cast {
10512+
kind: CastKind::DoubleColon,
10513+
expr: Box::new(Expr::Value(Value::SingleQuotedString(
10514+
"2 seconds".to_string()
10515+
))),
10516+
data_type: DataType::Interval,
10517+
format: None,
10518+
}),
10519+
}
10520+
)
10521+
}
10522+
10523+
#[test]
10524+
fn test_extract_seconds_err() {
10525+
let sql = "SELECT EXTRACT(seconds FROM '2 seconds'::INTERVAL)";
10526+
let dialects = all_dialects_except(|d| d.allow_extract_custom());
10527+
let err = dialects.parse_sql_statements(sql).unwrap_err();
10528+
assert_eq!(
10529+
err.to_string(),
10530+
"sql parser error: Expected: date/time field, found: seconds"
10531+
);
10532+
}
10533+
10534+
#[test]
10535+
fn test_extract_seconds_single_quote_err() {
10536+
let sql = r#"SELECT EXTRACT('seconds' FROM '2 seconds'::INTERVAL)"#;
10537+
let dialects = all_dialects_except(|d| d.allow_extract_single_quotes());
10538+
let err = dialects.parse_sql_statements(sql).unwrap_err();
10539+
assert_eq!(
10540+
err.to_string(),
10541+
"sql parser error: Expected: date/time field, found: 'seconds'"
10542+
);
10543+
}

0 commit comments

Comments
 (0)