Skip to content

Commit 1a0556f

Browse files
committed
ISSUE-1147: Add support for MATERIALIZED CTEs
In Postgres, the [NOT] MATERIALIZED keywords can be applied when creating CTEs. Add support for them.
1 parent 92781c1 commit 1a0556f

File tree

5 files changed

+75
-9
lines changed

5 files changed

+75
-9
lines changed

src/ast/mod.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ pub use self::ddl::{
3939
};
4040
pub use self::operator::{BinaryOperator, UnaryOperator};
4141
pub use self::query::{
42-
Cte, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause, ForJson, ForXml,
43-
GroupByExpr, IdentWithAlias, Join, JoinConstraint, JoinOperator, JsonTableColumn,
44-
JsonTableColumnErrorHandling, LateralView, LockClause, LockType, NamedWindowDefinition,
45-
NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem, ReplaceSelectElement,
46-
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table,
47-
TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity, Values,
48-
WildcardAdditionalOptions, With,
42+
Cte, CteAsMaterialized, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause,
43+
ForJson, ForXml, GroupByExpr, IdentWithAlias, Join, JoinConstraint, JoinOperator,
44+
JsonTableColumn, JsonTableColumnErrorHandling, LateralView, LockClause, LockType,
45+
NamedWindowDefinition, NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem,
46+
ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator,
47+
SetQuantifier, Table, TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity,
48+
Values, WildcardAdditionalOptions, With,
4949
};
5050
pub use self::value::{
5151
escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value,

src/ast/query.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,36 @@ impl fmt::Display for With {
376376
}
377377
}
378378

379-
/// A single CTE (used after `WITH`): `alias [(col1, col2, ...)] AS ( query )`
379+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
380+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
381+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
382+
pub enum CteAsMaterialized {
383+
/// The WITH statement does not specify MATERIALIZED behavior
384+
Default,
385+
/// The WITH statement specifies AS MATERIALIZED behavior
386+
Materialized,
387+
/// The WITH statement specifies AS NOT MATERIALIZED behavior
388+
NotMaterialized,
389+
}
390+
391+
impl fmt::Display for CteAsMaterialized {
392+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
393+
match *self {
394+
CteAsMaterialized::Default => {
395+
write!(f, "")?;
396+
}
397+
CteAsMaterialized::Materialized => {
398+
write!(f, "MATERIALIZED")?;
399+
}
400+
CteAsMaterialized::NotMaterialized => {
401+
write!(f, "NOT MATERIALIZED")?;
402+
}
403+
};
404+
Ok(())
405+
}
406+
}
407+
408+
/// A single CTE (used after `WITH`): `<alias> [(col1, col2, ...)] AS <materialized> ( <query> )`
380409
/// The names in the column list before `AS`, when specified, replace the names
381410
/// of the columns returned by the query. The parser does not validate that the
382411
/// number of columns in the query matches the number of columns in the query.
@@ -387,11 +416,20 @@ pub struct Cte {
387416
pub alias: TableAlias,
388417
pub query: Box<Query>,
389418
pub from: Option<Ident>,
419+
pub materialized: CteAsMaterialized,
390420
}
391421

392422
impl fmt::Display for Cte {
393423
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
394-
write!(f, "{} AS ({})", self.alias, self.query)?;
424+
if matches!(self.materialized, CteAsMaterialized::Default) {
425+
write!(f, "{} AS ({})", self.alias, self.query)?;
426+
} else {
427+
write!(
428+
f,
429+
"{} AS {} ({})",
430+
self.alias, self.materialized, self.query
431+
)?;
432+
}
395433
if let Some(ref fr) = self.from {
396434
write!(f, " FROM {fr}")?;
397435
}

src/parser/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6668,6 +6668,14 @@ impl<'a> Parser<'a> {
66686668
let name = self.parse_identifier(false)?;
66696669

66706670
let mut cte = if self.parse_keyword(Keyword::AS) {
6671+
let mut is_materialized = CteAsMaterialized::Default;
6672+
if dialect_of!(self is PostgreSqlDialect) {
6673+
if self.parse_keyword(Keyword::MATERIALIZED) {
6674+
is_materialized = CteAsMaterialized::Materialized;
6675+
} else if self.parse_keywords(&[Keyword::NOT, Keyword::MATERIALIZED]) {
6676+
is_materialized = CteAsMaterialized::NotMaterialized;
6677+
}
6678+
}
66716679
self.expect_token(&Token::LParen)?;
66726680
let query = Box::new(self.parse_query()?);
66736681
self.expect_token(&Token::RParen)?;
@@ -6679,10 +6687,19 @@ impl<'a> Parser<'a> {
66796687
alias,
66806688
query,
66816689
from: None,
6690+
materialized: is_materialized,
66826691
}
66836692
} else {
66846693
let columns = self.parse_parenthesized_column_list(Optional, false)?;
66856694
self.expect_keyword(Keyword::AS)?;
6695+
let mut is_materialized = CteAsMaterialized::Default;
6696+
if dialect_of!(self is PostgreSqlDialect) {
6697+
if self.parse_keyword(Keyword::MATERIALIZED) {
6698+
is_materialized = CteAsMaterialized::Materialized;
6699+
} else if self.parse_keywords(&[Keyword::NOT, Keyword::MATERIALIZED]) {
6700+
is_materialized = CteAsMaterialized::NotMaterialized;
6701+
}
6702+
}
66866703
self.expect_token(&Token::LParen)?;
66876704
let query = Box::new(self.parse_query()?);
66886705
self.expect_token(&Token::RParen)?;
@@ -6691,6 +6708,7 @@ impl<'a> Parser<'a> {
66916708
alias,
66926709
query,
66936710
from: None,
6711+
materialized: is_materialized,
66946712
}
66956713
};
66966714
if self.parse_keyword(Keyword::FROM) {

tests/sqlparser_common.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5586,6 +5586,7 @@ fn parse_recursive_cte() {
55865586
},
55875587
query: Box::new(cte_query),
55885588
from: None,
5589+
materialized: CteAsMaterialized::Default,
55895590
};
55905591
assert_eq!(with.cte_tables.first().unwrap(), &expected);
55915592
}

tests/sqlparser_postgres.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3834,3 +3834,12 @@ fn parse_array_agg() {
38343834
let sql4 = "SELECT ARRAY_AGG(my_schema.sections_tbl.*) AS sections FROM sections_tbl";
38353835
pg().verified_stmt(sql4);
38363836
}
3837+
3838+
#[test]
3839+
fn parse_mat_cte() {
3840+
let sql = r#"WITH cte AS MATERIALIZED (SELECT id FROM accounts) SELECT id FROM cte"#;
3841+
pg().verified_stmt(sql);
3842+
3843+
let sql2 = r#"WITH cte AS NOT MATERIALIZED (SELECT id FROM accounts) SELECT id FROM cte"#;
3844+
pg().verified_stmt(sql2);
3845+
}

0 commit comments

Comments
 (0)