Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2356,6 +2356,9 @@ impl fmt::Display for WindowSpec {
} else {
write!(f, "{} {}", window_frame.units, window_frame.start_bound)?;
}
if let Some(exclusion) = &window_frame.exclusion {
write!(f, " {exclusion}")?;
}
}
Ok(())
}
Expand All @@ -2378,7 +2381,8 @@ pub struct WindowFrame {
/// indicates the shorthand form (e.g. `ROWS 1 PRECEDING`), which must
/// behave the same as `end_bound = WindowFrameBound::CurrentRow`.
pub end_bound: Option<WindowFrameBound>,
// TBD: EXCLUDE
/// The optional exclusion clause for the window frame.
pub exclusion: Option<WindowFrameExclusion>,
}

impl Default for WindowFrame {
Expand All @@ -2390,6 +2394,7 @@ impl Default for WindowFrame {
units: WindowFrameUnits::Range,
start_bound: WindowFrameBound::Preceding(None),
end_bound: None,
exclusion: None,
}
}
}
Expand Down Expand Up @@ -2465,6 +2470,32 @@ impl fmt::Display for WindowFrameBound {
}
}

/// Specifies rows to exclude from a window frame.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum WindowFrameExclusion {
/// `EXCLUDE CURRENT ROW`
CurrentRow,
/// `EXCLUDE GROUP`
Group,
/// `EXCLUDE TIES`
Ties,
/// `EXCLUDE NO OTHERS`
NoOthers,
}

impl fmt::Display for WindowFrameExclusion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe move EXCLUDE to a single line

WindowFrameExclusion::CurrentRow => "EXCLUDE CURRENT ROW",
WindowFrameExclusion::Group => "EXCLUDE GROUP",
WindowFrameExclusion::Ties => "EXCLUDE TIES",
WindowFrameExclusion::NoOthers => "EXCLUDE NO OTHERS",
})
}
}

#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ impl Dialect for GenericDialect {
true
}

fn supports_window_frame_exclusion(&self) -> bool {
true
}

fn supports_limit_comma(&self) -> bool {
true
}
Expand Down
7 changes: 7 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,13 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if the dialect supports window frame exclusions, e.g.
/// `ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES`.
/// See <https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS>.
fn supports_window_frame_exclusion(&self) -> bool {
false
}

/// Returns true if the dialect supports the `LOAD DATA` statement
fn supports_load_data(&self) -> bool {
false
Expand Down
5 changes: 5 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@ impl Dialect for PostgreSqlDialect {
true
}

/// see <https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS>
fn supports_window_frame_exclusion(&self) -> bool {
true
}

/// see <https://www.postgresql.org/docs/13/functions-math.html>
fn supports_factorial_operator(&self) -> bool {
true
Expand Down
26 changes: 26 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2705,13 +2705,39 @@ impl<'a> Parser<'a> {
} else {
(self.parse_window_frame_bound()?, None)
};
let exclusion = if self.dialect.supports_window_frame_exclusion()
&& self.parse_keyword(Keyword::EXCLUDE)
{
Some(self.parse_window_frame_exclusion()?)
} else {
None
};
Ok(WindowFrame {
units,
start_bound,
end_bound,
exclusion,
})
}

/// Parse a window frame exclusion clause following `EXCLUDE`.
pub fn parse_window_frame_exclusion(&mut self) -> Result<WindowFrameExclusion, ParserError> {
if self.parse_keywords(&[Keyword::CURRENT, Keyword::ROW]) {
Ok(WindowFrameExclusion::CurrentRow)
} else if self.parse_keyword(Keyword::GROUP) {
Ok(WindowFrameExclusion::Group)
} else if self.parse_keyword(Keyword::TIES) {
Ok(WindowFrameExclusion::Ties)
} else if self.parse_keyword(Keyword::NO)
&& self.consume_token(&Token::make_word("OTHERS", None))
{
Ok(WindowFrameExclusion::NoOthers)
} else {
let next_token = self.next_token();
self.expected("CURRENT ROW, GROUP, TIES, or NO OTHERS", next_token)
}
}

/// Parse a window frame bound: `CURRENT ROW` or `<n> PRECEDING|FOLLOWING`.
pub fn parse_window_frame_bound(&mut self) -> Result<WindowFrameBound, ParserError> {
if self.parse_keywords(&[Keyword::CURRENT, Keyword::ROW]) {
Expand Down
28 changes: 28 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9953,3 +9953,31 @@ fn parse_insert_by_name_keywords_as_table_and_alias() {
statement => panic!("Expected INSERT statement, got: {statement:?}"),
}
}

#[test]
fn parse_window_frame_exclusion() {
let dialects = pg_and_generic();
for sql in [
"SELECT sum(1) OVER (ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES)",
"SELECT sum(1) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT ROW)",
"SELECT sum(1) OVER (ROWS CURRENT ROW EXCLUDE GROUP)",
"SELECT sum(1) OVER (ROWS CURRENT ROW EXCLUDE NO OTHERS)",
] {
dialects.verified_stmt(sql);
}

let invalid = "SELECT sum(1) OVER (ROWS CURRENT ROW EXCLUDE ALL)";
assert_eq!(
pg().parse_sql_statements(invalid).unwrap_err(),
ParserError::ParserError(
"Expected: CURRENT ROW, GROUP, TIES, or NO OTHERS, found: ALL".to_string()
)
);

let unsupported = all_dialects_where(|d| !d.supports_window_frame_exclusion());
let sql = "SELECT sum(1) OVER (ROWS CURRENT ROW EXCLUDE TIES)";
for dialect in unsupported.dialects {
let parser = TestedDialects::new(vec![dialect]);
assert!(parser.parse_sql_statements(sql).is_err());
}
}
Loading