Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for INTERVAL inside window frames #655

Merged
merged 5 commits into from Oct 15, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 24 additions & 3 deletions src/ast/mod.rs
Expand Up @@ -880,9 +880,9 @@ pub enum WindowFrameBound {
/// `CURRENT ROW`
CurrentRow,
/// `<N> PRECEDING` or `UNBOUNDED PRECEDING`
Preceding(Option<u64>),
Preceding(Option<RangeBounds>),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this in general can be any Expression (not just a Number of Interval):

https://www.postgresql.org/docs/current/sql-expressions.html

In the offset PRECEDING and offset FOLLOWING frame options, the offset must be an expression not containing any variables, aggregate functions, or window functions. The meaning of the offset depends on the frame mode:

What do you think about just parsing it as an Expression rather than special casing Number / Interval?

/// `<N> FOLLOWING` or `UNBOUNDED FOLLOWING`.
Following(Option<u64>),
Following(Option<RangeBounds>),
}

impl fmt::Display for WindowFrameBound {
Expand All @@ -897,6 +897,27 @@ impl fmt::Display for WindowFrameBound {
}
}

/// Specifies offset values in the [WindowFrameBound]'s `Preceding` and `Following`
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum RangeBounds {
/// Number literal, e.g `1`, `1.1`
Number(String),
/// Interval, such as `INTERVAL '1 DAY' `
Interval(Expr),
}

impl fmt::Display for RangeBounds {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RangeBounds::Number(s) => write!(f, "{}", s),
RangeBounds::Interval(interval) => {
write!(f, "{}", interval)
}
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum AddDropSync {
Expand Down Expand Up @@ -2670,7 +2691,7 @@ impl fmt::Display for CloseCursor {
pub struct Function {
pub name: ObjectName,
pub args: Vec<FunctionArg>,
pub over: Option<WindowSpec>,
pub over: Option<Box<WindowSpec>>,
// aggregate functions may specify eg `COUNT(DISTINCT x)`
pub distinct: bool,
// Some functions must be called without trailing parentheses, for example Postgres
Expand Down
40 changes: 38 additions & 2 deletions src/parser.rs
Expand Up @@ -620,7 +620,7 @@ impl<'a> Parser<'a> {
} else {
None
};

let over = over.map(Box::new);
Ok(Expr::Function(Function {
name,
args,
Expand Down Expand Up @@ -682,7 +682,7 @@ impl<'a> Parser<'a> {
let rows = if self.parse_keyword(Keyword::UNBOUNDED) {
None
} else {
Some(self.parse_literal_uint()?)
Some(self.parse_range_values()?)
};
if self.parse_keyword(Keyword::PRECEDING) {
Ok(WindowFrameBound::Preceding(rows))
Expand Down Expand Up @@ -3319,6 +3319,42 @@ impl<'a> Parser<'a> {
}
}

/// Parse a literal inside window frames such as 1 PRECEDING or '1 DAY' PRECEDING
pub fn parse_range_values(&mut self) -> Result<RangeBounds, ParserError> {
match self.peek_token() {
Token::Number(s, _) => {
// consume token Token::Number
self.next_token();
Ok(RangeBounds::Number(s))
}
Token::Word(w) => match w.keyword {
Keyword::INTERVAL => {
// consume token Keyword::INTERVAL
self.next_token();
match self.parse_interval() {
Ok(interval) => Ok(RangeBounds::Interval(interval)),
e => Err(ParserError::ParserError(format!(
"Interval is not valid {:?}",
e
))),
}
}
e => Err(ParserError::ParserError(format!(
"Interval is not valid {:?}",
e
))),
},
Token::SingleQuotedString(_) => match self.parse_interval() {
Ok(interval) => Ok(RangeBounds::Interval(interval)),
e => Err(ParserError::ParserError(format!(
"Interval is not valid {:?}",
e
))),
},
unexpected => self.expected("literal Expression", unexpected),
}
}

/// Parse a literal string
pub fn parse_literal_string(&mut self) -> Result<String, ParserError> {
match self.next_token() {
Expand Down
14 changes: 9 additions & 5 deletions tests/sqlparser_common.rs
Expand Up @@ -1555,15 +1555,15 @@ fn parse_select_qualify() {
left: Box::new(Expr::Function(Function {
name: ObjectName(vec![Ident::new("ROW_NUMBER")]),
args: vec![],
over: Some(WindowSpec {
over: Some(Box::new(WindowSpec {
partition_by: vec![Expr::Identifier(Ident::new("p"))],
order_by: vec![OrderByExpr {
expr: Expr::Identifier(Ident::new("o")),
asc: None,
nulls_first: None
}],
window_frame: None
}),
})),
distinct: false,
special: false
})),
Expand Down Expand Up @@ -2865,26 +2865,30 @@ fn parse_window_functions() {
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), \
avg(bar) OVER (ORDER BY a \
RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING), \
sum(bar) OVER (ORDER BY a \
RANGE BETWEEN INTERVAL '1' DAY PRECEDING AND INTERVAL '1 MONTH' FOLLOWING), \
COUNT(*) OVER (ORDER BY a \
RANGE BETWEEN INTERVAL '1 DAYS' PRECEDING AND INTERVAL '1 DAY' FOLLOWING), \
max(baz) OVER (ORDER BY a \
ROWS UNBOUNDED PRECEDING), \
sum(qux) OVER (ORDER BY a \
GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) \
FROM foo";
let select = verified_only_select(sql);
assert_eq!(5, select.projection.len());
assert_eq!(7, select.projection.len());
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("row_number")]),
args: vec![],
over: Some(WindowSpec {
over: Some(Box::new(WindowSpec {
partition_by: vec![],
order_by: vec![OrderByExpr {
expr: Expr::Identifier(Ident::new("dt")),
asc: Some(false),
nulls_first: None,
}],
window_frame: None,
}),
})),
distinct: false,
special: false,
}),
Expand Down