Skip to content

Commit c0c2ae5

Browse files
committed
Redshift: CREATE TABLE ... (LIKE ..)
1 parent 60a5c8d commit c0c2ae5

File tree

10 files changed

+659
-10
lines changed

10 files changed

+659
-10
lines changed

src/ast/dml.rs

Lines changed: 462 additions & 1 deletion
Large diffs are not rendered by default.

src/ast/helpers/stmt_create_table.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize};
2525
use sqlparser_derive::{Visit, VisitMut};
2626

2727
use crate::ast::{
28-
ClusteredBy, ColumnDef, CommentDef, CreateTable, CreateTableOptions, Expr, FileFormat,
28+
ClusteredBy, ColumnDef, CommentDef, CreateTable, CreateTableLikeKind, CreateTableOptions, Expr, FileFormat,
2929
HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit, OneOrManyWithParens, Query,
3030
RowAccessPolicy, Statement, StorageSerializationPolicy, TableConstraint, Tag,
3131
WrappedCollection,
@@ -81,7 +81,7 @@ pub struct CreateTableBuilder {
8181
pub location: Option<String>,
8282
pub query: Option<Box<Query>>,
8383
pub without_rowid: bool,
84-
pub like: Option<ObjectName>,
84+
pub like: Option<CreateTableLikeKind>,
8585
pub clone: Option<ObjectName>,
8686
pub comment: Option<CommentDef>,
8787
pub on_commit: Option<OnCommit>,
@@ -237,7 +237,7 @@ impl CreateTableBuilder {
237237
self
238238
}
239239

240-
pub fn like(mut self, like: Option<ObjectName>) -> Self {
240+
pub fn like(mut self, like: Option<CreateTableLikeKind>) -> Self {
241241
self.like = like;
242242
self
243243
}

src/ast/mod.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10465,6 +10465,63 @@ impl fmt::Display for CreateUser {
1046510465
}
1046610466
}
1046710467

10468+
/// Specifies how to create a new table based on an existing table's schema.
10469+
///
10470+
/// Not parenthesized:
10471+
/// '''sql
10472+
/// CREATE TABLE new LIKE old ...
10473+
/// '''
10474+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-table#label-create-table-like)
10475+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_like)
10476+
///
10477+
/// Parenthesized:
10478+
/// '''sql
10479+
/// CREATE TABLE new (LIKE old ...)
10480+
/// '''
10481+
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html)
10482+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10483+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10484+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10485+
pub enum CreateTableLikeKind {
10486+
Parenthesized(CreateTableLike),
10487+
NotParenthesized(CreateTableLike),
10488+
}
10489+
10490+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10491+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10492+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10493+
pub enum CreateTableLikeDefaults {
10494+
Including,
10495+
Excluding,
10496+
}
10497+
10498+
impl fmt::Display for CreateTableLikeDefaults {
10499+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
10500+
match self {
10501+
CreateTableLikeDefaults::Including => write!(f, "INCLUDING DEFAULTS"),
10502+
CreateTableLikeDefaults::Excluding => write!(f, "EXCLUDING DEFAULTS"),
10503+
}
10504+
}
10505+
}
10506+
10507+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10508+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10509+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10510+
pub struct CreateTableLike {
10511+
pub name: ObjectName,
10512+
pub defaults: Option<CreateTableLikeDefaults>,
10513+
}
10514+
10515+
impl fmt::Display for CreateTableLike {
10516+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
10517+
write!(f, "LIKE {}", self.name)?;
10518+
if let Some(defaults) = &self.defaults {
10519+
write!(f, " {defaults}")?;
10520+
}
10521+
Ok(())
10522+
}
10523+
}
10524+
1046810525
#[cfg(test)]
1046910526
mod tests {
1047010527
use crate::tokenizer::Location;

src/ast/spans.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ impl Spanned for CreateTable {
592592
location: _, // string, no span
593593
query,
594594
without_rowid: _, // bool
595-
like,
595+
like: _,
596596
clone,
597597
comment: _, // todo, no span
598598
on_commit: _,
@@ -627,7 +627,6 @@ impl Spanned for CreateTable {
627627
.chain(columns.iter().map(|i| i.span()))
628628
.chain(constraints.iter().map(|i| i.span()))
629629
.chain(query.iter().map(|i| i.span()))
630-
.chain(like.iter().map(|i| i.span()))
631630
.chain(clone.iter().map(|i| i.span())),
632631
)
633632
}

src/dialect/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,25 @@ pub trait Dialect: Debug + Any {
11631163
fn supports_interval_options(&self) -> bool {
11641164
false
11651165
}
1166+
1167+
/// Returns true if the dialect supports specifying which table to copy
1168+
/// the schema from inside parenthesis.
1169+
///
1170+
/// Not parenthesized:
1171+
/// '''sql
1172+
/// CREATE TABLE new LIKE old ...
1173+
/// '''
1174+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-table#label-create-table-like)
1175+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_like)
1176+
///
1177+
/// Parenthesized:
1178+
/// '''sql
1179+
/// CREATE TABLE new (LIKE old ...)
1180+
/// '''
1181+
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html)
1182+
fn supports_create_table_like_in_parens(&self) -> bool {
1183+
false
1184+
}
11661185
}
11671186

11681187
/// This represents the operators for which precedence must be defined

src/dialect/redshift.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,8 @@ impl Dialect for RedshiftSqlDialect {
139139
fn supports_select_exclude(&self) -> bool {
140140
true
141141
}
142+
143+
fn supports_create_table_like_in_parens(&self) -> bool {
144+
true
145+
}
142146
}

src/dialect/snowflake.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,8 +668,13 @@ pub fn parse_create_table(
668668
builder = builder.clone_clause(clone);
669669
}
670670
Keyword::LIKE => {
671-
let like = parser.parse_object_name(false).ok();
672-
builder = builder.like(like);
671+
let name = parser.parse_object_name(false)?;
672+
builder = builder.like(Some(CreateTableLikeKind::NotParenthesized(
673+
crate::ast::CreateTableLike {
674+
name,
675+
defaults: None,
676+
},
677+
)));
673678
}
674679
Keyword::CLUSTER => {
675680
parser.expect_keyword_is(Keyword::BY)?;

src/keywords.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ define_keywords!(
268268
DECLARE,
269269
DEDUPLICATE,
270270
DEFAULT,
271+
DEFAULTS,
271272
DEFAULT_DDL_COLLATION,
272273
DEFERRABLE,
273274
DEFERRED,
@@ -339,6 +340,7 @@ define_keywords!(
339340
EXCEPTION,
340341
EXCHANGE,
341342
EXCLUDE,
343+
EXCLUDING,
342344
EXCLUSIVE,
343345
EXEC,
344346
EXECUTE,
@@ -441,6 +443,7 @@ define_keywords!(
441443
IN,
442444
INCLUDE,
443445
INCLUDE_NULL_VALUES,
446+
INCLUDING,
444447
INCREMENT,
445448
INDEX,
446449
INDICATOR,

src/parser/mod.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7347,8 +7347,35 @@ impl<'a> Parser<'a> {
73477347
// Clickhouse has `ON CLUSTER 'cluster'` syntax for DDLs
73487348
let on_cluster = self.parse_optional_on_cluster()?;
73497349

7350-
let like = if self.parse_keyword(Keyword::LIKE) || self.parse_keyword(Keyword::ILIKE) {
7351-
self.parse_object_name(allow_unquoted_hyphen).ok()
7350+
// Try to parse `CREATE TABLE new (LIKE old [{INCLUDING | EXCLUDING} DEFAULTS])` or `CREATE TABLE new LIKE old`
7351+
let like = if self.dialect.supports_create_table_like_in_parens()
7352+
&& self.consume_token(&Token::LParen)
7353+
{
7354+
if self.parse_keyword(Keyword::LIKE) {
7355+
let name = self.parse_object_name(allow_unquoted_hyphen)?;
7356+
let defaults = if self.parse_keywords(&[Keyword::INCLUDING, Keyword::DEFAULTS]) {
7357+
Some(CreateTableLikeDefaults::Including)
7358+
} else if self.parse_keywords(&[Keyword::EXCLUDING, Keyword::DEFAULTS]) {
7359+
Some(CreateTableLikeDefaults::Excluding)
7360+
} else {
7361+
None
7362+
};
7363+
self.expect_token(&Token::RParen)?;
7364+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
7365+
name,
7366+
defaults,
7367+
}))
7368+
} else {
7369+
// Rollback the '(' it's probably the columns list
7370+
self.prev_token();
7371+
None
7372+
}
7373+
} else if self.parse_keyword(Keyword::LIKE) || self.parse_keyword(Keyword::ILIKE) {
7374+
let name = self.parse_object_name(allow_unquoted_hyphen)?;
7375+
Some(CreateTableLikeKind::NotParenthesized(CreateTableLike {
7376+
name,
7377+
defaults: None,
7378+
}))
73527379
} else {
73537380
None
73547381
};

tests/sqlparser_common.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16655,3 +16655,77 @@ fn test_parse_default_with_collate_column_option() {
1665516655
panic!("Expected create table statement");
1665616656
}
1665716657
}
16658+
16659+
#[test]
16660+
fn parse_create_table_like() {
16661+
let dialects = all_dialects_except(|d| d.supports_create_table_like_in_parens());
16662+
let sql = "CREATE TABLE new LIKE old";
16663+
match dialects.verified_stmt(sql) {
16664+
Statement::CreateTable(stmt) => {
16665+
assert_eq!(
16666+
stmt.name,
16667+
ObjectName::from(vec![Ident::new("new".to_string())])
16668+
);
16669+
assert_eq!(
16670+
stmt.like,
16671+
Some(CreateTableLikeKind::NotParenthesized(CreateTableLike {
16672+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16673+
defaults: None,
16674+
}))
16675+
)
16676+
}
16677+
_ => unreachable!(),
16678+
}
16679+
let dialects = all_dialects_where(|d| d.supports_create_table_like_in_parens());
16680+
let sql = "CREATE TABLE new (LIKE old)";
16681+
match dialects.verified_stmt(sql) {
16682+
Statement::CreateTable(stmt) => {
16683+
assert_eq!(
16684+
stmt.name,
16685+
ObjectName::from(vec![Ident::new("new".to_string())])
16686+
);
16687+
assert_eq!(
16688+
stmt.like,
16689+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
16690+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16691+
defaults: None,
16692+
}))
16693+
)
16694+
}
16695+
_ => unreachable!(),
16696+
}
16697+
let sql = "CREATE TABLE new (LIKE old INCLUDING DEFAULTS)";
16698+
match dialects.verified_stmt(sql) {
16699+
Statement::CreateTable(stmt) => {
16700+
assert_eq!(
16701+
stmt.name,
16702+
ObjectName::from(vec![Ident::new("new".to_string())])
16703+
);
16704+
assert_eq!(
16705+
stmt.like,
16706+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
16707+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16708+
defaults: Some(CreateTableLikeDefaults::Including),
16709+
}))
16710+
)
16711+
}
16712+
_ => unreachable!(),
16713+
}
16714+
let sql = "CREATE TABLE new (LIKE old EXCLUDING DEFAULTS)";
16715+
match dialects.verified_stmt(sql) {
16716+
Statement::CreateTable(stmt) => {
16717+
assert_eq!(
16718+
stmt.name,
16719+
ObjectName::from(vec![Ident::new("new".to_string())])
16720+
);
16721+
assert_eq!(
16722+
stmt.like,
16723+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
16724+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16725+
defaults: Some(CreateTableLikeDefaults::Excluding),
16726+
}))
16727+
)
16728+
}
16729+
_ => unreachable!(),
16730+
}
16731+
}

0 commit comments

Comments
 (0)