Skip to content
Merged
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
3 changes: 3 additions & 0 deletions fuzz/fuzz_seeds/postgres_2769_refresh_materialized_view.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
REFRESH MATERIALIZED VIEW m;
REFRESH MATERIALIZED VIEW CONCURRENTLY app.m WITH NO DATA;
REFRESH MATERIALIZED VIEW m WITH DATA
45 changes: 45 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5113,6 +5113,13 @@ pub enum Statement {
/// ```
/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-reset.html)
Reset(ResetStatement),
/// Replace the contents of a materialized view.
///
/// ```sql
/// REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] name [ WITH [ NO ] DATA ]
/// ```
/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-refreshmaterializedview.html)
RefreshMaterializedView(RefreshMaterializedView),
}

impl From<Analyze> for Statement {
Expand Down Expand Up @@ -6589,6 +6596,7 @@ impl fmt::Display for Statement {
Statement::Vacuum(s) => write!(f, "{s}"),
Statement::AlterUser(s) => write!(f, "{s}"),
Statement::Reset(s) => write!(f, "{s}"),
Statement::RefreshMaterializedView(s) => write!(f, "{s}"),
}
}
}
Expand Down Expand Up @@ -12364,6 +12372,37 @@ pub struct ResetStatement {
pub reset: Reset,
}

/// `REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] name [ WITH [ NO ] DATA ]`
///
/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-refreshmaterializedview.html)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct RefreshMaterializedView {
/// Whether `CONCURRENTLY` was specified.
pub concurrently: bool,
/// Name of the materialized view.
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
pub name: ObjectName,
/// `Some(true)` for `WITH DATA`, `Some(false)` for `WITH NO DATA`.
pub with_data: Option<bool>,
}

impl fmt::Display for RefreshMaterializedView {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("REFRESH MATERIALIZED VIEW ")?;
if self.concurrently {
f.write_str("CONCURRENTLY ")?;
}
write!(f, "{}", self.name)?;
match self.with_data {
Some(true) => f.write_str(" WITH DATA"),
Some(false) => f.write_str(" WITH NO DATA"),
None => Ok(()),
}
}
}

/// Query optimizer hints are optionally supported comments after the
/// `SELECT`, `INSERT`, `UPDATE`, `REPLACE`, `MERGE`, and `DELETE` keywords in
/// the corresponding statements.
Expand Down Expand Up @@ -12796,6 +12835,12 @@ impl From<ResetStatement> for Statement {
}
}

impl From<RefreshMaterializedView> for Statement {
fn from(r: RefreshMaterializedView) -> Self {
Self::RefreshMaterializedView(r)
}
}

#[cfg(test)]
mod tests {
use crate::tokenizer::Location;
Expand Down
19 changes: 13 additions & 6 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ use super::{
NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction,
OnInsert, OpenStatement, OperateFunctionArg, OrderBy, OrderByExpr, OrderByKind, OutputClause,
Parens, Partition, PartitionBoundValue, PivotValueSource, ProjectionSelect, Query,
RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement,
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, Setting, SqlOption, Statement,
Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor,
TableObject, TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use,
UserMappingUser, Values, ViewColumnDef, WhileStatement, WildcardAdditionalOptions, With,
WithFill,
RaiseStatement, RaiseStatementValue, ReferentialAction, RefreshMaterializedView,
RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
SetExpr, Setting, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias,
TableAliasColumnDef, TableConstraint, TableFactor, TableObject, TableOptionsClustered,
TableWithJoins, Update, UpdateTableFromKind, Use, UserMappingUser, Values, ViewColumnDef,
WhileStatement, WildcardAdditionalOptions, With, WithFill,
};

/// Given an iterator of spans, return the [Span::union] of all spans.
Expand Down Expand Up @@ -525,6 +525,7 @@ impl Spanned for Statement {
Statement::Vacuum(..) => Span::empty(),
Statement::AlterUser(..) => Span::empty(),
Statement::Reset(..) => Span::empty(),
Statement::RefreshMaterializedView(refresh) => refresh.span(),
Statement::CreateAggregate(create_aggregate) => create_aggregate.span(),
}
}
Expand Down Expand Up @@ -939,6 +940,12 @@ impl Spanned for Analyze {
}
}

impl Spanned for RefreshMaterializedView {
fn span(&self) -> Span {
self.name.span()
}
}

/// # partial span
///
/// Missing spans:
Expand Down
20 changes: 20 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,7 @@ impl<'a> Parser<'a> {
self.parse_vacuum()
}
Keyword::RESET => self.parse_reset().map(Into::into),
Keyword::REFRESH => self.parse_refresh_materialized_view().map(Into::into),
_ => self.expected("an SQL statement", next_token),
},
Token::LParen => {
Expand Down Expand Up @@ -22426,6 +22427,25 @@ impl<'a> Parser<'a> {
reset: Reset::ConfigurationParameter(obj),
})
}

/// Parse a `REFRESH MATERIALIZED VIEW` statement, after the `REFRESH` keyword.
fn parse_refresh_materialized_view(&mut self) -> Result<RefreshMaterializedView, ParserError> {
self.expect_keywords(&[Keyword::MATERIALIZED, Keyword::VIEW])?;
let concurrently = self.parse_keyword(Keyword::CONCURRENTLY);
let name = self.parse_object_name(false)?;
let with_data = if self.parse_keywords(&[Keyword::WITH, Keyword::DATA]) {
Some(true)
} else if self.parse_keywords(&[Keyword::WITH, Keyword::NO, Keyword::DATA]) {
Some(false)
} else {
None
};
Ok(RefreshMaterializedView {
concurrently,
name,
with_data,
})
}
}

fn maybe_prefixed_expr(expr: Expr, prefix: Option<Ident>) -> Expr {
Expand Down
94 changes: 94 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23106,3 +23106,97 @@ fn parse_alter_table_reset_options() {
);
}
}

#[test]
fn parse_refresh_materialized_view() {
let sql = "REFRESH MATERIALIZED VIEW CONCURRENTLY app.m WITH NO DATA";
assert_eq!(
Parser::parse_sql(&GenericDialect {}, sql).unwrap()[0].span(),
Span::new(Location::new(1, 40), Location::new(1, 45))
);
match verified_stmt(sql) {
Statement::RefreshMaterializedView(RefreshMaterializedView {
concurrently,
name,
with_data,
}) => {
assert!(concurrently);
assert_eq!(
name,
ObjectName::from(vec![Ident::new("app"), Ident::new("m")])
);
assert_eq!(with_data, Some(false));
}
stmt => panic!("unexpected statement: {stmt:?}"),
}
match verified_stmt("REFRESH MATERIALIZED VIEW m") {
Statement::RefreshMaterializedView(RefreshMaterializedView {
concurrently,
with_data,
..
}) => {
assert!(!concurrently);
assert_eq!(with_data, None);
}
stmt => panic!("unexpected statement: {stmt:?}"),
}
match verified_stmt("REFRESH MATERIALIZED VIEW m WITH DATA") {
Statement::RefreshMaterializedView(RefreshMaterializedView { with_data, .. }) => {
assert_eq!(with_data, Some(true));
}
stmt => panic!("unexpected statement: {stmt:?}"),
}

for (sql, error) in [
(
"REFRESH MATERIALIZED VIEW",
"Expected: identifier, found: EOF",
),
("REFRESH VIEW m", "Expected: MATERIALIZED, found: VIEW"),
(
"REFRESH MATERIALIZED VIEW m WITH",
"Expected: end of statement, found: WITH",
),
] {
assert_eq!(
all_dialects().parse_sql_statements(sql).unwrap_err(),
ParserError::ParserError(error.to_string()),
"{sql}"
);
}
}

#[test]
#[cfg(feature = "visitor")]
fn test_visit_relations_refresh_materialized_view() {
let stmts = all_dialects().parse_sql_statements("REFRESH MATERIALIZED VIEW app.m");
let mut relations = vec![];
let _ = visit_relations(&stmts.unwrap(), |relation| {
relations.push(relation.to_string());
core::ops::ControlFlow::<()>::Continue(())
});
assert_eq!(relations, vec!["app.m"]);
}

#[test]
fn display_refresh_materialized_view_propagates_write_errors() {
use core::fmt::{self, Write};

struct RejectingWriter(&'static str);

impl Write for RejectingWriter {
fn write_str(&mut self, value: &str) -> fmt::Result {
if value.contains(self.0) {
Err(fmt::Error)
} else {
Ok(())
}
}
}

let statement = verified_stmt("REFRESH MATERIALIZED VIEW CONCURRENTLY app.m WITH NO DATA");
for rejected in ["REFRESH", "CONCURRENTLY", "app", "NO DATA"] {
let mut writer = RejectingWriter(rejected);
assert_eq!(write!(writer, "{statement}"), Err(fmt::Error), "{rejected}");
}
}
Loading