Skip to content

Commit 72ef486

Browse files
authored
fix(redshift): fix INTERVAL quoting (#5545)
Signed-off-by: Luka Peschke <mail@lukapeschke.com>
1 parent a666ec1 commit 72ef486

3 files changed

Lines changed: 85 additions & 31 deletions

File tree

prqlc/prqlc/src/sql/dialect.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::any::{Any, TypeId};
1616

1717
use chrono::format::{Fixed, Item, Numeric, Pad, StrftimeItems};
1818
use serde::{Deserialize, Serialize};
19+
use sqlparser::ast::DateTimeField;
1920
use strum::VariantNames;
2021

2122
use crate::{Error, Result};
@@ -141,6 +142,16 @@ pub enum IdentQuotingStyle {
141142
ConditionallyQuoted,
142143
}
143144

145+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146+
pub enum IntervalQuotingStyle {
147+
// INTERVAL 1 day
148+
NoQuotes,
149+
// INTERVAL '1' day
150+
ValueQuoted,
151+
// INTERVAL '1 day'
152+
ValueAndUnitQuoted,
153+
}
154+
144155
pub(super) trait DialectHandler: Any + Debug {
145156
fn use_fetch(&self) -> bool {
146157
false
@@ -181,9 +192,9 @@ pub(super) trait DialectHandler: Any + Debug {
181192
}
182193

183194
/// Whether or not intervals such as `INTERVAL 1 HOUR` require quotes like
184-
/// `INTERVAL '1 HOUR'`
185-
fn requires_quotes_intervals(&self) -> bool {
186-
false
195+
/// `INTERVAL '1 HOUR'` or `INTERVAL '1' HOUR`
196+
fn interval_quoting_style(&self, _dtf: &DateTimeField) -> IntervalQuotingStyle {
197+
IntervalQuotingStyle::NoQuotes
187198
}
188199

189200
/// Support for GROUP BY *
@@ -249,8 +260,8 @@ impl DialectHandler for GenericDialect {
249260
}
250261

251262
impl DialectHandler for PostgresDialect {
252-
fn requires_quotes_intervals(&self) -> bool {
253-
true
263+
fn interval_quoting_style(&self, _dtf: &DateTimeField) -> IntervalQuotingStyle {
264+
IntervalQuotingStyle::ValueAndUnitQuoted
254265
}
255266

256267
fn supports_distinct_on(&self) -> bool {
@@ -314,8 +325,12 @@ impl DialectHandler for RedshiftDialect {
314325
IdentQuotingStyle::ConditionallyQuoted
315326
}
316327

317-
fn requires_quotes_intervals(&self) -> bool {
318-
true
328+
fn interval_quoting_style(&self, dtf: &DateTimeField) -> IntervalQuotingStyle {
329+
if matches!(dtf, DateTimeField::Week(_) | DateTimeField::Weeks) {
330+
IntervalQuotingStyle::ValueAndUnitQuoted
331+
} else {
332+
IntervalQuotingStyle::ValueQuoted
333+
}
319334
}
320335

321336
fn supports_distinct_on(&self) -> bool {
@@ -380,8 +395,8 @@ impl DialectHandler for RedshiftDialect {
380395
}
381396

382397
impl DialectHandler for GlareDbDialect {
383-
fn requires_quotes_intervals(&self) -> bool {
384-
true
398+
fn interval_quoting_style(&self, _dtf: &DateTimeField) -> IntervalQuotingStyle {
399+
IntervalQuotingStyle::ValueAndUnitQuoted
385400
}
386401
}
387402

prqlc/prqlc/src/sql/gen_expr.rs

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use super::{keywords, Context};
1616
use crate::ir::generic::{ColumnSort, SortDirection, WindowFrame, WindowKind};
1717
use crate::ir::pl::{self, Ident, Literal};
1818
use crate::ir::rq;
19-
use crate::sql::dialect::IdentQuotingStyle;
19+
use crate::sql::dialect::{IdentQuotingStyle, IntervalQuotingStyle};
2020
use crate::sql::pq::context::ColumnDecl;
2121
use crate::utils::{valid_ident, OrMap};
2222
use crate::{Error, Result, Span, WithErrorInfo};
@@ -428,27 +428,46 @@ pub(super) fn translate_literal(l: Literal, ctx: &Context) -> Result<sql_ast::Ex
428428
)))
429429
}
430430
};
431-
if ctx.dialect.requires_quotes_intervals() {
432-
//postgres requires quotes around number and unit together eg '3 WEEK'
433-
let value = Box::new(sql_ast::Expr::Value(
434-
Value::SingleQuotedString(format!("{} {}", vau.n, sql_parser_datetime)).into(),
435-
));
436-
sql_ast::Expr::Interval(sqlparser::ast::Interval {
437-
value,
438-
leading_field: None, //set to none since field is now contained in string
439-
leading_precision: None,
440-
last_field: None,
441-
fractional_seconds_precision: None,
442-
})
443-
} else {
444-
let value = Box::new(translate_literal(Literal::Integer(vau.n), ctx)?);
445-
sql_ast::Expr::Interval(sqlparser::ast::Interval {
446-
value,
447-
leading_field: Some(sql_parser_datetime),
448-
leading_precision: None,
449-
last_field: None,
450-
fractional_seconds_precision: None,
451-
})
431+
match ctx.dialect.interval_quoting_style(&sql_parser_datetime) {
432+
IntervalQuotingStyle::ValueAndUnitQuoted => {
433+
//postgres requires quotes around number and unit together eg '3 WEEK'
434+
let value = Box::new(sql_ast::Expr::Value(
435+
Value::SingleQuotedString(format!("{} {}", vau.n, sql_parser_datetime))
436+
.into(),
437+
));
438+
sql_ast::Expr::Interval(sqlparser::ast::Interval {
439+
value,
440+
leading_field: None, //set to none since field is now contained in string
441+
leading_precision: None,
442+
last_field: None,
443+
fractional_seconds_precision: None,
444+
})
445+
}
446+
IntervalQuotingStyle::NoQuotes => {
447+
let value = Box::new(translate_literal(Literal::Integer(vau.n), ctx)?);
448+
sql_ast::Expr::Interval(sqlparser::ast::Interval {
449+
value,
450+
leading_field: Some(sql_parser_datetime),
451+
leading_precision: None,
452+
last_field: None,
453+
fractional_seconds_precision: None,
454+
})
455+
}
456+
// Redshift requires quotes around the number only, otherwise months and years are
457+
// not supported. eg '3' MONTH
458+
IntervalQuotingStyle::ValueQuoted => {
459+
// Adding single quotes around the number
460+
let value = Box::new(sql_ast::Expr::Value(
461+
Value::SingleQuotedString(vau.n.to_string()).into(),
462+
));
463+
sql_ast::Expr::Interval(sqlparser::ast::Interval {
464+
value,
465+
leading_field: Some(sql_parser_datetime),
466+
leading_precision: None,
467+
last_field: None,
468+
fractional_seconds_precision: None,
469+
})
470+
}
452471
}
453472
}
454473
})

prqlc/prqlc/tests/integration/sql.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6380,6 +6380,26 @@ fn test_redshift_uses_double_pipe_over_concat() {
63806380
");
63816381
}
63826382

6383+
#[rstest]
6384+
#[case(
6385+
"from t | select { inter = 2weeks }",
6386+
"SELECT\n INTERVAL '2 WEEK' AS inter\nFROM\n t\n"
6387+
)]
6388+
#[case(
6389+
"from t | select { inter = 2months }",
6390+
"SELECT\n INTERVAL '2' MONTH AS inter\nFROM\n t\n"
6391+
)]
6392+
#[case(
6393+
"from t | select { inter = 2hours }",
6394+
"SELECT\n INTERVAL '2' HOUR AS inter\nFROM\n t\n"
6395+
)]
6396+
fn test_redshift_interval_quoting(#[case] query: &str, #[case] expected: &str) {
6397+
assert_eq!(
6398+
compile_with_sql_dialect(query, sql::Dialect::Redshift).unwrap(),
6399+
expected
6400+
)
6401+
}
6402+
63836403
#[test]
63846404
fn test_redshift_text_contains_uses_double_pipe() {
63856405
assert_snapshot!(compile_with_sql_dialect(r###"

0 commit comments

Comments
 (0)