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
19 changes: 18 additions & 1 deletion src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,14 @@ pub enum AlterTableOperation {
/// Table properties specified as SQL options.
table_properties: Vec<SqlOption>,
},
/// `SET LOGGED`
///
/// Note: this is PostgreSQL-specific.
SetLogged,
/// `SET UNLOGGED`
///
/// Note: this is PostgreSQL-specific.
SetUnlogged,
/// `OWNER TO { <new_owner> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }`
///
/// Note: this is PostgreSQL-specific <https://www.postgresql.org/docs/current/sql-altertable.html>
Expand Down Expand Up @@ -971,6 +979,12 @@ impl fmt::Display for AlterTableOperation {
display_comma_separated(table_properties)
)
}
AlterTableOperation::SetLogged => {
write!(f, "SET LOGGED")
}
AlterTableOperation::SetUnlogged => {
write!(f, "SET UNLOGGED")
}
AlterTableOperation::FreezePartition {
partition,
with_name,
Expand Down Expand Up @@ -2898,6 +2912,8 @@ pub struct CreateTable {
pub or_replace: bool,
/// `TEMP` or `TEMPORARY` clause
pub temporary: bool,
/// `UNLOGGED` clause
pub unlogged: bool,
/// `EXTERNAL` clause
pub external: bool,
/// `DYNAMIC` clause
Expand Down Expand Up @@ -3089,7 +3105,7 @@ impl fmt::Display for CreateTable {
// `CREATE TABLE t (a INT) AS SELECT a from t2`
write!(
f,
"CREATE {or_replace}{external}{global}{multiset}{temporary}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE {if_not_exists}{name}",
"CREATE {or_replace}{external}{multiset}{global}{temporary}{unlogged}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE {if_not_exists}{name}",
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
external = if self.external { "EXTERNAL " } else { "" },
snapshot = if self.snapshot { "SNAPSHOT " } else { "" },
Expand All @@ -3108,6 +3124,7 @@ impl fmt::Display for CreateTable {
.map(|m| if m { "MULTISET " } else { "SET " })
.unwrap_or(""),
temporary = if self.temporary { "TEMPORARY " } else { "" },
unlogged = if self.unlogged { "UNLOGGED " } else { "" },
transient = if self.transient { "TRANSIENT " } else { "" },
volatile = if self.volatile { "VOLATILE " } else { "" },
iceberg = if self.iceberg { "ICEBERG " } else { "" },
Expand Down
10 changes: 10 additions & 0 deletions src/ast/helpers/stmt_create_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ pub struct CreateTableBuilder {
pub or_replace: bool,
/// Whether the table is `TEMPORARY`.
pub temporary: bool,
/// Whether the table is `UNLOGGED`.
pub unlogged: bool,
/// Whether the table is `EXTERNAL`.
pub external: bool,
/// Optional `GLOBAL` flag for dialects that support it.
Expand Down Expand Up @@ -200,6 +202,7 @@ impl CreateTableBuilder {
Self {
or_replace: false,
temporary: false,
unlogged: false,
external: false,
global: None,
if_not_exists: false,
Expand Down Expand Up @@ -273,6 +276,11 @@ impl CreateTableBuilder {
self.temporary = temporary;
self
}
/// Mark the table as `UNLOGGED`.
pub fn unlogged(mut self, unlogged: bool) -> Self {
self.unlogged = unlogged;
self
}
/// Mark the table as `EXTERNAL`.
pub fn external(mut self, external: bool) -> Self {
self.external = external;
Expand Down Expand Up @@ -595,6 +603,7 @@ impl CreateTableBuilder {
CreateTable {
or_replace: self.or_replace,
temporary: self.temporary,
unlogged: self.unlogged,
external: self.external,
global: self.global,
if_not_exists: self.if_not_exists,
Expand Down Expand Up @@ -680,6 +689,7 @@ impl From<CreateTable> for CreateTableBuilder {
Self {
or_replace: table.or_replace,
temporary: table.temporary,
unlogged: table.unlogged,
external: table.external,
global: table.global,
if_not_exists: table.if_not_exists,
Expand Down
3 changes: 3 additions & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ impl Spanned for CreateTable {
let CreateTable {
or_replace: _, // bool
temporary: _, // bool
unlogged: _, // bool
external: _, // bool
global: _, // bool
dynamic: _, // bool
Expand Down Expand Up @@ -1216,6 +1217,8 @@ impl Spanned for AlterTableOperation {
AlterTableOperation::SetTblProperties { table_properties } => {
union_spans(table_properties.iter().map(|i| i.span()))
}
AlterTableOperation::SetLogged => Span::empty(),
AlterTableOperation::SetUnlogged => Span::empty(),
AlterTableOperation::OwnerTo { .. } => Span::empty(),
AlterTableOperation::ClusterBy { exprs } => union_spans(exprs.iter().map(|e| e.span())),
AlterTableOperation::DropClusteringKey => Span::empty(),
Expand Down
1 change: 1 addition & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ define_keywords!(
LOCK,
LOCKED,
LOG,
LOGGED,
LOGIN,
LOGS,
LONG,
Expand Down
11 changes: 11 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5144,6 +5144,12 @@ impl<'a> Parser<'a> {
let create_view_params = self.parse_create_view_params()?;
if self.peek_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) {
self.parse_create_snapshot_table().map(Into::into)
} else if self.peek_keywords(&[Keyword::UNLOGGED, Keyword::TABLE]) {
self.expect_keywords(&[Keyword::UNLOGGED, Keyword::TABLE])?;
let mut create_table = self
.parse_create_table(or_replace, temporary, global, transient, volatile, multiset)?;
create_table.unlogged = true;
Ok(create_table.into())
} else if self.parse_keyword(Keyword::TABLE) {
self.parse_create_table(or_replace, temporary, global, transient, volatile, multiset)
.map(Into::into)
Expand Down Expand Up @@ -8664,6 +8670,7 @@ impl<'a> Parser<'a> {

Ok(CreateTableBuilder::new(table_name)
.temporary(temporary)
.unlogged(false)
.columns(columns)
.constraints(constraints)
.or_replace(or_replace)
Expand Down Expand Up @@ -10745,6 +10752,10 @@ impl<'a> Parser<'a> {
} else if self.parse_keywords(&[Keyword::VALIDATE, Keyword::CONSTRAINT]) {
let name = self.parse_identifier()?;
AlterTableOperation::ValidateConstraint { name }
} else if self.parse_keywords(&[Keyword::SET, Keyword::LOGGED]) {
AlterTableOperation::SetLogged
} else if self.parse_keywords(&[Keyword::SET, Keyword::UNLOGGED]) {
AlterTableOperation::SetUnlogged
} else {
let mut options =
self.parse_options_with_keywords(&[Keyword::SET, Keyword::TBLPROPERTIES])?;
Expand Down
24 changes: 24 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18951,3 +18951,27 @@ fn parse_non_pg_dialects_keep_xml_names_as_regular_identifiers() {
let dialects = all_dialects_except(|d| d.supports_xml_expressions());
dialects.verified_only_select("SELECT xml FROM t");
}

#[test]
fn parse_unlogged_table_logging_controls_in_all_dialects() {
match all_dialects().verified_stmt("CREATE UNLOGGED TABLE t (a INT)") {
Statement::CreateTable(CreateTable { unlogged, .. }) => {
assert!(unlogged);
}
_ => unreachable!("Expected CREATE TABLE"),
}

match all_dialects().verified_stmt("ALTER TABLE t SET LOGGED") {
Statement::AlterTable(AlterTable { operations, .. }) => {
assert_eq!(vec![AlterTableOperation::SetLogged], operations);
}
_ => unreachable!("Expected ALTER TABLE"),
}

match all_dialects().verified_stmt("ALTER TABLE t SET UNLOGGED") {
Statement::AlterTable(AlterTable { operations, .. }) => {
assert_eq!(vec![AlterTableOperation::SetUnlogged], operations);
}
_ => unreachable!("Expected ALTER TABLE"),
}
}
1 change: 1 addition & 0 deletions tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@ fn test_duckdb_union_datatype() {
Statement::CreateTable(CreateTable {
or_replace: Default::default(),
temporary: Default::default(),
unlogged: Default::default(),
external: Default::default(),
global: Default::default(),
if_not_exists: Default::default(),
Expand Down
2 changes: 2 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1924,6 +1924,7 @@ fn parse_create_table_with_valid_options() {
Statement::CreateTable(CreateTable {
or_replace: false,
temporary: false,
unlogged: false,
external: false,
global: None,
dynamic: false,
Expand Down Expand Up @@ -2121,6 +2122,7 @@ fn parse_create_table_with_identity_column() {
Statement::CreateTable(CreateTable {
or_replace: false,
temporary: false,
unlogged: false,
external: false,
global: None,
dynamic: false,
Expand Down
79 changes: 79 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,33 @@ fn parse_alter_table_owner_to() {
);
}

#[test]
fn parse_alter_table_set_logged_unlogged() {
let sql = "ALTER TABLE unlogged1 SET LOGGED";
match pg_and_generic().verified_stmt(sql) {
Statement::AlterTable(AlterTable {
name, operations, ..
}) => {
assert_eq!("unlogged1", name.to_string());
assert_eq!(vec![AlterTableOperation::SetLogged], operations);
}
_ => unreachable!(),
}
pg_and_generic().one_statement_parses_to(sql, sql);

let sql = "ALTER TABLE unlogged1 SET UNLOGGED";
match pg_and_generic().verified_stmt(sql) {
Statement::AlterTable(AlterTable {
name, operations, ..
}) => {
assert_eq!("unlogged1", name.to_string());
assert_eq!(vec![AlterTableOperation::SetUnlogged], operations);
}
_ => unreachable!(),
}
pg_and_generic().one_statement_parses_to(sql, sql);
}

#[test]
fn parse_create_table_if_not_exists() {
let sql = "CREATE TABLE IF NOT EXISTS uk_cities ()";
Expand Down Expand Up @@ -5692,6 +5719,57 @@ fn parse_create_table_with_partition_by() {
}
}

#[test]
fn parse_create_unlogged_table() {
let sql = "CREATE UNLOGGED TABLE public.unlogged2 (a int primary key)";
match pg_and_generic().one_statement_parses_to(
sql,
"CREATE UNLOGGED TABLE public.unlogged2 (a INT PRIMARY KEY)",
) {
Statement::CreateTable(CreateTable { name, unlogged, .. }) => {
assert!(unlogged);
assert_eq!("public.unlogged2", name.to_string());
}
_ => unreachable!(),
}

let sql = "CREATE UNLOGGED TABLE pg_temp.unlogged3 (a int primary key)";
match pg_and_generic().one_statement_parses_to(
sql,
"CREATE UNLOGGED TABLE pg_temp.unlogged3 (a INT PRIMARY KEY)",
) {
Statement::CreateTable(CreateTable { name, unlogged, .. }) => {
assert!(unlogged);
assert_eq!("pg_temp.unlogged3", name.to_string());
}
_ => unreachable!(),
}

let sql = "CREATE UNLOGGED TABLE unlogged1 (a int) PARTITION BY RANGE (a)";
match pg_and_generic().one_statement_parses_to(
sql,
"CREATE UNLOGGED TABLE unlogged1 (a INT) PARTITION BY RANGE(a)",
) {
Statement::CreateTable(CreateTable {
name,
unlogged,
partition_by,
..
}) => {
assert!(unlogged);
assert_eq!("unlogged1", name.to_string());
assert!(partition_by.is_some());
}
_ => unreachable!(),
}

let res = pg().parse_sql_statements("CREATE UNLOGGED VIEW v AS SELECT 1");
assert_eq!(
ParserError::ParserError("Expected: an object type after CREATE, found: UNLOGGED".into()),
res.unwrap_err()
);
}

#[test]
fn parse_join_constraint_unnest_alias() {
assert_eq!(
Expand Down Expand Up @@ -6638,6 +6716,7 @@ fn parse_trigger_related_functions() {
CreateTable {
or_replace: false,
temporary: false,
unlogged: false,
external: false,
global: None,
dynamic: false,
Expand Down
Loading