Skip to content

Commit 8e40ac9

Browse files
committed
bq: named window specs
1 parent 5e1a628 commit 8e40ac9

3 files changed

Lines changed: 97 additions & 42 deletions

File tree

src/ast/mod.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -445,16 +445,24 @@ impl fmt::Display for Expr {
445445
}
446446
}
447447

448+
/// A window specification, either inline or named
449+
/// https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#window_clause
450+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
451+
pub enum WindowSpec {
452+
Inline(InlineWindowSpec),
453+
Named(Ident),
454+
}
455+
448456
/// A window specification (i.e. `OVER (PARTITION BY .. ORDER BY .. etc.)`)
449457
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
450458
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
451-
pub struct WindowSpec {
459+
pub struct InlineWindowSpec {
452460
pub partition_by: Vec<Expr>,
453461
pub order_by: Vec<OrderByExpr>,
454462
pub window_frame: Option<WindowFrame>,
455463
}
456464

457-
impl fmt::Display for WindowSpec {
465+
impl fmt::Display for InlineWindowSpec {
458466
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
459467
let mut delim = "";
460468
if !self.partition_by.is_empty() {
@@ -1061,8 +1069,10 @@ impl fmt::Display for Function {
10611069
display_comma_separated(&self.within_group)
10621070
)?;
10631071
}
1064-
if let Some(o) = &self.over {
1065-
write!(f, " OVER ({})", o)?;
1072+
match &self.over {
1073+
Some(WindowSpec::Inline(over)) => write!(f, " OVER ({})", over)?,
1074+
Some(WindowSpec::Named(name)) => write!(f, " OVER {}", name)?,
1075+
None => {}
10661076
}
10671077
Ok(())
10681078
}

src/ast/query.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ pub struct Select {
136136
pub having: Option<Expr>,
137137
/// QUALIFY https://docs.snowflake.com/en/sql-reference/constructs/qualify.html
138138
pub qualify: Option<Expr>,
139+
/// WINDOW https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#window_clause
140+
pub windows: Vec<(Ident, WindowSpec)>,
139141
}
140142

141143
impl fmt::Display for Select {
@@ -160,6 +162,18 @@ impl fmt::Display for Select {
160162
if let Some(ref qualify) = self.qualify {
161163
write!(f, " QUALIFY {}", qualify)?;
162164
}
165+
if !self.windows.is_empty() {
166+
write!(f, " WINDOW ")?;
167+
let mut delim = "";
168+
for (ident, spec) in self.windows.iter() {
169+
write!(f, "{}{} AS ", delim, ident)?;
170+
match spec {
171+
WindowSpec::Inline(inline) => write!(f, "({})", inline)?,
172+
WindowSpec::Named(name) => write!(f, "{}", name)?,
173+
}
174+
delim = ", ";
175+
}
176+
}
163177
Ok(())
164178
}
165179
}

src/parser.rs

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -362,32 +362,7 @@ impl<'a> Parser<'a> {
362362
vec![]
363363
};
364364
let over = if self.parse_keyword(Keyword::OVER) {
365-
// TBD: support window names (`OVER mywin`) in place of inline specification
366-
self.expect_token(&Token::LParen)?;
367-
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
368-
// a list of possibly-qualified column names
369-
self.parse_comma_separated(Parser::parse_expr)?
370-
} else {
371-
vec![]
372-
};
373-
let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
374-
self.parse_comma_separated(Parser::parse_order_by_expr)?
375-
} else {
376-
vec![]
377-
};
378-
let window_frame = if !self.consume_token(&Token::RParen) {
379-
let window_frame = self.parse_window_frame()?;
380-
self.expect_token(&Token::RParen)?;
381-
Some(window_frame)
382-
} else {
383-
None
384-
};
385-
386-
Some(WindowSpec {
387-
partition_by,
388-
order_by,
389-
window_frame,
390-
})
365+
Some(self.parse_window_spec()?)
391366
} else {
392367
None
393368
};
@@ -438,20 +413,68 @@ impl<'a> Parser<'a> {
438413
}
439414
}
440415

441-
pub fn parse_window_frame_units(&mut self) -> Result<WindowFrameUnits, ParserError> {
442-
match self.next_token() {
416+
pub fn parse_named_window_expr(&mut self) -> Result<(Ident, WindowSpec), ParserError> {
417+
let ident = self.parse_identifier()?;
418+
self.expect_keyword(Keyword::AS)?;
419+
let spec = self.parse_window_spec()?;
420+
return Ok((ident, spec));
421+
}
422+
423+
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
424+
if self.consume_token(&Token::LParen) {
425+
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
426+
// a list of possibly-qualified column names
427+
self.parse_comma_separated(Parser::parse_expr)?
428+
} else {
429+
vec![]
430+
};
431+
let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
432+
self.parse_comma_separated(Parser::parse_order_by_expr)?
433+
} else {
434+
vec![]
435+
};
436+
let window_frame = self.parse_window_frame()?;
437+
438+
let found_rparen = self.consume_token(&Token::RParen);
439+
if partition_by.is_empty() && order_by.is_empty() && window_frame.is_none() && !found_rparen {
440+
// try parsing a named window if we failed to parse any part of
441+
// a window spec and we haven't reached the rparen yet
442+
let ident = self.parse_identifier()?;
443+
self.expect_token(&Token::RParen)?;
444+
return Ok(WindowSpec::Named(ident));
445+
}
446+
447+
Ok(WindowSpec::Inline(InlineWindowSpec {
448+
partition_by,
449+
order_by,
450+
window_frame,
451+
}))
452+
} else {
453+
// named windows don't need parens
454+
Ok(WindowSpec::Named(self.parse_identifier()?))
455+
}
456+
}
457+
458+
pub fn consume_window_frame_units(&mut self) -> Result<Option<WindowFrameUnits>, ParserError> {
459+
let units = match self.peek_token() {
443460
Token::Word(w) => match w.keyword {
444-
Keyword::ROWS => Ok(WindowFrameUnits::Rows),
445-
Keyword::RANGE => Ok(WindowFrameUnits::Range),
446-
Keyword::GROUPS => Ok(WindowFrameUnits::Groups),
447-
_ => self.expected("ROWS, RANGE, GROUPS", Token::Word(w))?,
461+
Keyword::ROWS => WindowFrameUnits::Rows,
462+
Keyword::RANGE => WindowFrameUnits::Range,
463+
Keyword::GROUPS => WindowFrameUnits::Groups,
464+
_ => return Ok(None),
448465
},
449-
unexpected => self.expected("ROWS, RANGE, GROUPS", unexpected),
450-
}
466+
_ => return Ok(None),
467+
};
468+
self.next_token(); // consume token
469+
Ok(Some(units))
451470
}
452471

453-
pub fn parse_window_frame(&mut self) -> Result<WindowFrame, ParserError> {
454-
let units = self.parse_window_frame_units()?;
472+
pub fn parse_window_frame(&mut self) -> Result<Option<WindowFrame>, ParserError> {
473+
let units = if let Some(units) = self.consume_window_frame_units()? {
474+
units
475+
} else {
476+
return Ok(None);
477+
};
455478
let (start_bound, end_bound) = if self.parse_keyword(Keyword::BETWEEN) {
456479
let start_bound = self.parse_window_frame_bound()?;
457480
self.expect_keyword(Keyword::AND)?;
@@ -460,11 +483,11 @@ impl<'a> Parser<'a> {
460483
} else {
461484
(self.parse_window_frame_bound()?, None)
462485
};
463-
Ok(WindowFrame {
486+
Ok(Some(WindowFrame {
464487
units,
465488
start_bound,
466489
end_bound,
467-
})
490+
}))
468491
}
469492

470493
/// Parse `CURRENT ROW` or `{ <positive number> | UNBOUNDED } { PRECEDING | FOLLOWING }`
@@ -2324,6 +2347,13 @@ impl<'a> Parser<'a> {
23242347
None
23252348
};
23262349

2350+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#window_clause
2351+
let windows = if self.parse_keyword(Keyword::WINDOW) {
2352+
self.parse_comma_separated(Parser::parse_named_window_expr)?
2353+
} else {
2354+
vec![]
2355+
};
2356+
23272357
Ok(Select {
23282358
distinct,
23292359
top,
@@ -2333,6 +2363,7 @@ impl<'a> Parser<'a> {
23332363
group_by,
23342364
having,
23352365
qualify,
2366+
windows,
23362367
})
23372368
}
23382369

0 commit comments

Comments
 (0)