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 @@ -965,6 +973,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 @@ -2889,6 +2903,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 @@ -3045,7 +3061,7 @@ impl fmt::Display for CreateTable {
// `CREATE TABLE t (a INT) AS SELECT a from t2`
write!(
f,
"CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}{dynamic}{iceberg}TABLE {if_not_exists}{name}",
"CREATE {or_replace}{external}{global}{temporary}{unlogged}{transient}{volatile}{dynamic}{iceberg}TABLE {if_not_exists}{name}",
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
external = if self.external { "EXTERNAL " } else { "" },
global = self.global
Expand All @@ -3059,6 +3075,7 @@ impl fmt::Display for CreateTable {
.unwrap_or(""),
if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { "" },
temporary = if self.temporary { "TEMPORARY " } else { "" },
unlogged = if self.unlogged { "UNLOGGED " } else { "" },
transient = if self.transient { "TRANSIENT " } else { "" },
volatile = if self.volatile { "VOLATILE " } else { "" },
// Only for Snowflake
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 @@ -68,6 +68,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 @@ -178,6 +180,7 @@ impl CreateTableBuilder {
Self {
or_replace: false,
temporary: false,
unlogged: false,
external: false,
global: None,
if_not_exists: false,
Expand Down Expand Up @@ -241,6 +244,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 @@ -509,6 +517,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 @@ -584,6 +593,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 @@ -531,6 +531,7 @@ impl Spanned for CreateTable {
let CreateTable {
or_replace: _, // bool
temporary: _, // bool
unlogged: _, // bool
external: _, // bool
global: _, // bool
dynamic: _, // bool
Expand Down Expand Up @@ -1184,6 +1185,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 @@ -587,6 +587,7 @@ define_keywords!(
LOCK,
LOCKED,
LOG,
LOGGED,
LOGIN,
LOGS,
LONG,
Expand Down
185 changes: 94 additions & 91 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,10 +508,10 @@ impl<'a> Parser<'a> {
Token::EOF => break,

// end of statement
Token::Word(word) => {
if expecting_statement_delimiter && word.keyword == Keyword::END {
break;
}
Token::Word(word)
if expecting_statement_delimiter && word.keyword == Keyword::END =>
{
break;
}
_ => {}
}
Expand Down Expand Up @@ -1298,41 +1298,40 @@ impl<'a> Parser<'a> {

let next_token = self.next_token();
match next_token.token {
t @ (Token::Word(_) | Token::SingleQuotedString(_)) => {
if self.peek_token_ref().token == Token::Period {
let mut id_parts: Vec<Ident> = vec![match t {
Token::Word(w) => w.into_ident(next_token.span),
Token::SingleQuotedString(s) => Ident::with_quote('\'', s),
_ => {
return Err(ParserError::ParserError(
"Internal parser error: unexpected token type".to_string(),
))
t @ (Token::Word(_) | Token::SingleQuotedString(_))
if self.peek_token_ref().token == Token::Period =>
{
let mut id_parts: Vec<Ident> = vec![match t {
Token::Word(w) => w.into_ident(next_token.span),
Token::SingleQuotedString(s) => Ident::with_quote('\'', s),
_ => {
return Err(ParserError::ParserError(
"Internal parser error: unexpected token type".to_string(),
))
}
}];

while self.consume_token(&Token::Period) {
let next_token = self.next_token();
match next_token.token {
Token::Word(w) => id_parts.push(w.into_ident(next_token.span)),
Token::SingleQuotedString(s) => {
// SQLite has single-quoted identifiers
id_parts.push(Ident::with_quote('\'', s))
}
}];

while self.consume_token(&Token::Period) {
let next_token = self.next_token();
match next_token.token {
Token::Word(w) => id_parts.push(w.into_ident(next_token.span)),
Token::SingleQuotedString(s) => {
// SQLite has single-quoted identifiers
id_parts.push(Ident::with_quote('\'', s))
}
Token::Placeholder(s) => {
// Snowflake uses $1, $2, etc. for positional column references
// in staged data queries like: SELECT t.$1 FROM @stage t
id_parts.push(Ident::new(s))
}
Token::Mul => {
return Ok(Expr::QualifiedWildcard(
ObjectName::from(id_parts),
AttachedToken(next_token),
));
}
_ => {
return self
.expected("an identifier or a '*' after '.'", next_token);
}
Token::Placeholder(s) => {
// Snowflake uses $1, $2, etc. for positional column references
// in staged data queries like: SELECT t.$1 FROM @stage t
id_parts.push(Ident::new(s))
}
Token::Mul => {
return Ok(Expr::QualifiedWildcard(
ObjectName::from(id_parts),
AttachedToken(next_token),
));
}
_ => {
return self.expected("an identifier or a '*' after '.'", next_token);
}
}
}
Expand Down Expand Up @@ -4990,10 +4989,10 @@ impl<'a> Parser<'a> {
loop {
match &self.peek_nth_token_ref(0).token {
Token::EOF => break,
Token::Word(w) => {
if w.quote_style.is_none() && terminal_keywords.contains(&w.keyword) {
break;
}
Token::Word(w)
if w.quote_style.is_none() && terminal_keywords.contains(&w.keyword) =>
{
break;
}
_ => {}
}
Expand Down Expand Up @@ -5097,12 +5096,16 @@ impl<'a> Parser<'a> {
let temporary = self
.parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY])
.is_some();
let unlogged = dialect_of!(self is PostgreSqlDialect | GenericDialect)
&& self.parse_keyword(Keyword::UNLOGGED);
let persistent = dialect_of!(self is DuckDbDialect)
&& self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
let create_view_params = self.parse_create_view_params()?;
if self.parse_keyword(Keyword::TABLE) {
self.parse_create_table(or_replace, temporary, global, transient)
self.parse_create_table(or_replace, temporary, unlogged, global, transient)
.map(Into::into)
} else if unlogged {
self.expected_ref("TABLE after UNLOGGED", self.peek_token_ref())
} else if self.peek_keyword(Keyword::MATERIALIZED)
|| self.peek_keyword(Keyword::VIEW)
|| self.peek_keywords(&[Keyword::SECURE, Keyword::MATERIALIZED, Keyword::VIEW])
Expand Down Expand Up @@ -8173,70 +8176,60 @@ impl<'a> Parser<'a> {
Keyword::LINES,
Keyword::NULL,
]) {
Some(Keyword::FIELDS) => {
if self.parse_keywords(&[Keyword::TERMINATED, Keyword::BY]) {
Some(Keyword::FIELDS)
if self.parse_keywords(&[Keyword::TERMINATED, Keyword::BY]) =>
{
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::FieldsTerminatedBy,
char: self.parse_identifier()?,
});

if self.parse_keywords(&[Keyword::ESCAPED, Keyword::BY]) {
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::FieldsTerminatedBy,
delimiter: HiveDelimiter::FieldsEscapedBy,
char: self.parse_identifier()?,
});

if self.parse_keywords(&[Keyword::ESCAPED, Keyword::BY]) {
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::FieldsEscapedBy,
char: self.parse_identifier()?,
});
}
} else {
break;
}
}
Some(Keyword::COLLECTION) => {
Some(Keyword::COLLECTION)
if self.parse_keywords(&[
Keyword::ITEMS,
Keyword::TERMINATED,
Keyword::BY,
]) {
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::CollectionItemsTerminatedBy,
char: self.parse_identifier()?,
});
} else {
break;
}
]) =>
{
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::CollectionItemsTerminatedBy,
char: self.parse_identifier()?,
});
}
Some(Keyword::MAP) => {
Some(Keyword::MAP)
if self.parse_keywords(&[
Keyword::KEYS,
Keyword::TERMINATED,
Keyword::BY,
]) {
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::MapKeysTerminatedBy,
char: self.parse_identifier()?,
});
} else {
break;
}
]) =>
{
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::MapKeysTerminatedBy,
char: self.parse_identifier()?,
});
}
Some(Keyword::LINES) => {
if self.parse_keywords(&[Keyword::TERMINATED, Keyword::BY]) {
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::LinesTerminatedBy,
char: self.parse_identifier()?,
});
} else {
break;
}
Some(Keyword::LINES)
if self.parse_keywords(&[Keyword::TERMINATED, Keyword::BY]) =>
{
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::LinesTerminatedBy,
char: self.parse_identifier()?,
});
}
Some(Keyword::NULL) => {
if self.parse_keywords(&[Keyword::DEFINED, Keyword::AS]) {
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::NullDefinedAs,
char: self.parse_identifier()?,
});
} else {
break;
}
Some(Keyword::NULL)
if self.parse_keywords(&[Keyword::DEFINED, Keyword::AS]) =>
{
row_delimiters.push(HiveRowDelimiter {
delimiter: HiveDelimiter::NullDefinedAs,
char: self.parse_identifier()?,
});
}
_ => {
break;
Expand Down Expand Up @@ -8264,6 +8257,7 @@ impl<'a> Parser<'a> {
&mut self,
or_replace: bool,
temporary: bool,
unlogged: bool,
global: Option<bool>,
transient: bool,
) -> Result<CreateTable, ParserError> {
Expand Down Expand Up @@ -8382,6 +8376,7 @@ impl<'a> Parser<'a> {

Ok(CreateTableBuilder::new(table_name)
.temporary(temporary)
.unlogged(unlogged)
.columns(columns)
.constraints(constraints)
.or_replace(or_replace)
Expand Down Expand Up @@ -10376,6 +10371,14 @@ impl<'a> Parser<'a> {
} else if self.parse_keywords(&[Keyword::VALIDATE, Keyword::CONSTRAINT]) {
let name = self.parse_identifier()?;
AlterTableOperation::ValidateConstraint { name }
} else if dialect_of!(self is PostgreSqlDialect | GenericDialect)
&& self.parse_keywords(&[Keyword::SET, Keyword::LOGGED])
{
AlterTableOperation::SetLogged
} else if dialect_of!(self is PostgreSqlDialect | GenericDialect)
&& self.parse_keywords(&[Keyword::SET, Keyword::UNLOGGED])
{
AlterTableOperation::SetUnlogged
} else {
let mut options =
self.parse_options_with_keywords(&[Keyword::SET, Keyword::TBLPROPERTIES])?;
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,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
Loading