Skip to content

Commit 257f7d3

Browse files
committed
Redshift: CREATE TABLE ... (LIKE ..)
1 parent 4921846 commit 257f7d3

File tree

10 files changed

+210
-18
lines changed

10 files changed

+210
-18
lines changed

src/ast/dml.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ use serde::{Deserialize, Serialize};
2929
#[cfg(feature = "visitor")]
3030
use sqlparser_derive::{Visit, VisitMut};
3131

32-
use crate::display_utils::{indented_list, DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline};
32+
use crate::{
33+
ast::CreateTableLikeKind,
34+
display_utils::{indented_list, DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline},
35+
};
3336

3437
pub use super::ddl::{ColumnDef, TableConstraint};
3538

@@ -153,7 +156,7 @@ pub struct CreateTable {
153156
pub location: Option<String>,
154157
pub query: Option<Box<Query>>,
155158
pub without_rowid: bool,
156-
pub like: Option<ObjectName>,
159+
pub like: Option<CreateTableLikeKind>,
157160
pub clone: Option<ObjectName>,
158161
// For Hive dialect, the table comment is after the column definitions without `=`,
159162
// so the `comment` field is optional and different than the comment field in the general options list.
@@ -282,6 +285,8 @@ impl Display for CreateTable {
282285
} else if self.query.is_none() && self.like.is_none() && self.clone.is_none() {
283286
// PostgreSQL allows `CREATE TABLE t ();`, but requires empty parens
284287
f.write_str(" ()")?;
288+
} else if let Some(CreateTableLikeKind::Parenthesized(like_in_columns_list)) = &self.like {
289+
write!(f, " ({like_in_columns_list})")?;
285290
}
286291

287292
// Hive table comment should be after column definitions, please refer to:
@@ -295,9 +300,8 @@ impl Display for CreateTable {
295300
write!(f, " WITHOUT ROWID")?;
296301
}
297302

298-
// Only for Hive
299-
if let Some(l) = &self.like {
300-
write!(f, " LIKE {l}")?;
303+
if let Some(CreateTableLikeKind::NotParenthesized(like)) = &self.like {
304+
write!(f, " {like}")?;
301305
}
302306

303307
if let Some(c) = &self.clone {

src/ast/helpers/stmt_create_table.rs

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

2727
use super::super::dml::CreateTable;
2828
use crate::ast::{
29-
ClusteredBy, ColumnDef, CommentDef, CreateTableOptions, Expr, FileFormat,
29+
ClusteredBy, ColumnDef, CommentDef, CreateTableLikeKind, CreateTableOptions, Expr, FileFormat,
3030
HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit, OneOrManyWithParens, Query,
3131
RowAccessPolicy, Statement, StorageSerializationPolicy, TableConstraint, Tag,
3232
WrappedCollection,
@@ -82,7 +82,7 @@ pub struct CreateTableBuilder {
8282
pub location: Option<String>,
8383
pub query: Option<Box<Query>>,
8484
pub without_rowid: bool,
85-
pub like: Option<ObjectName>,
85+
pub like: Option<CreateTableLikeKind>,
8686
pub clone: Option<ObjectName>,
8787
pub comment: Option<CommentDef>,
8888
pub on_commit: Option<OnCommit>,
@@ -238,7 +238,7 @@ impl CreateTableBuilder {
238238
self
239239
}
240240

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

src/ast/mod.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10125,6 +10125,63 @@ impl fmt::Display for MemberOf {
1012510125
}
1012610126
}
1012710127

10128+
/// Specifies how to create a new table based on an existing table's schema.
10129+
///
10130+
/// Not parenthesized:
10131+
/// '''sql
10132+
/// CREATE TABLE new LIKE old ...
10133+
/// '''
10134+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-table#label-create-table-like)
10135+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_like)
10136+
///
10137+
/// Parenthesized:
10138+
/// '''sql
10139+
/// CREATE TABLE new (LIKE old ...)
10140+
/// '''
10141+
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html)
10142+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10143+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10144+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10145+
pub enum CreateTableLikeKind {
10146+
Parenthesized(CreateTableLike),
10147+
NotParenthesized(CreateTableLike),
10148+
}
10149+
10150+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10151+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10152+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10153+
pub enum CreateTableLikeDefaults {
10154+
Including,
10155+
Excluding,
10156+
}
10157+
10158+
impl fmt::Display for CreateTableLikeDefaults {
10159+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
10160+
match self {
10161+
CreateTableLikeDefaults::Including => write!(f, "INCLUDING DEFAULTS"),
10162+
CreateTableLikeDefaults::Excluding => write!(f, "EXCLUDING DEFAULTS"),
10163+
}
10164+
}
10165+
}
10166+
10167+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10168+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10169+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10170+
pub struct CreateTableLike {
10171+
pub name: ObjectName,
10172+
pub defaults: Option<CreateTableLikeDefaults>,
10173+
}
10174+
10175+
impl fmt::Display for CreateTableLike {
10176+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
10177+
write!(f, "LIKE {}", self.name)?;
10178+
if let Some(defaults) = &self.defaults {
10179+
write!(f, " {defaults}")?;
10180+
}
10181+
Ok(())
10182+
}
10183+
}
10184+
1012810185
#[cfg(test)]
1012910186
mod tests {
1013010187
use crate::tokenizer::Location;

src/ast/spans.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ impl Spanned for CreateTable {
575575
location: _, // string, no span
576576
query,
577577
without_rowid: _, // bool
578-
like,
578+
like: _,
579579
clone,
580580
comment: _, // todo, no span
581581
on_commit: _,
@@ -610,7 +610,6 @@ impl Spanned for CreateTable {
610610
.chain(columns.iter().map(|i| i.span()))
611611
.chain(constraints.iter().map(|i| i.span()))
612612
.chain(query.iter().map(|i| i.span()))
613-
.chain(like.iter().map(|i| i.span()))
614613
.chain(clone.iter().map(|i| i.span())),
615614
)
616615
}

src/dialect/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,25 @@ pub trait Dialect: Debug + Any {
11361136
fn supports_notnull_operator(&self) -> bool {
11371137
false
11381138
}
1139+
1140+
/// Returns true if the dialect supports specifying which table to copy
1141+
/// the schema from inside parenthesis.
1142+
///
1143+
/// Not parenthesized:
1144+
/// '''sql
1145+
/// CREATE TABLE new LIKE old ...
1146+
/// '''
1147+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-table#label-create-table-like)
1148+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_like)
1149+
///
1150+
/// Parenthesized:
1151+
/// '''sql
1152+
/// CREATE TABLE new (LIKE old ...)
1153+
/// '''
1154+
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html)
1155+
fn supports_create_table_like_in_parens(&self) -> bool {
1156+
false
1157+
}
11391158
}
11401159

11411160
/// 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: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ use crate::ast::helpers::stmt_data_loading::{
2323
FileStagingCommand, StageLoadSelectItem, StageLoadSelectItemKind, StageParamsObject,
2424
};
2525
use crate::ast::{
26-
ColumnOption, ColumnPolicy, ColumnPolicyProperty, CopyIntoSnowflakeKind, DollarQuotedString,
27-
Ident, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
28-
IdentityPropertyOrder, ObjectName, ObjectNamePart, RowAccessPolicy, ShowObjects, SqlOption,
29-
Statement, TagsColumnOption, WrappedCollection,
26+
ColumnOption, ColumnPolicy, ColumnPolicyProperty, CopyIntoSnowflakeKind, CreateTableLikeKind,
27+
DollarQuotedString, Ident, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind,
28+
IdentityPropertyKind, IdentityPropertyOrder, ObjectName, ObjectNamePart, RowAccessPolicy,
29+
ShowObjects, SqlOption, Statement, TagsColumnOption, WrappedCollection,
3030
};
3131
use crate::dialect::{Dialect, Precedence};
3232
use crate::keywords::Keyword;
@@ -577,8 +577,13 @@ pub fn parse_create_table(
577577
builder = builder.clone_clause(clone);
578578
}
579579
Keyword::LIKE => {
580-
let like = parser.parse_object_name(false).ok();
581-
builder = builder.like(like);
580+
let name = parser.parse_object_name(false)?;
581+
builder = builder.like(Some(CreateTableLikeKind::NotParenthesized(
582+
crate::ast::CreateTableLike {
583+
name,
584+
defaults: None,
585+
},
586+
)));
582587
}
583588
Keyword::CLUSTER => {
584589
parser.expect_keyword_is(Keyword::BY)?;

src/keywords.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ define_keywords!(
265265
DECLARE,
266266
DEDUPLICATE,
267267
DEFAULT,
268+
DEFAULTS,
268269
DEFAULT_DDL_COLLATION,
269270
DEFERRABLE,
270271
DEFERRED,
@@ -336,6 +337,7 @@ define_keywords!(
336337
EXCEPTION,
337338
EXCHANGE,
338339
EXCLUDE,
340+
EXCLUDING,
339341
EXCLUSIVE,
340342
EXEC,
341343
EXECUTE,
@@ -437,6 +439,7 @@ define_keywords!(
437439
IN,
438440
INCLUDE,
439441
INCLUDE_NULL_VALUES,
442+
INCLUDING,
440443
INCREMENT,
441444
INDEX,
442445
INDICATOR,

src/parser/mod.rs

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

7223-
let like = if self.parse_keyword(Keyword::LIKE) || self.parse_keyword(Keyword::ILIKE) {
7224-
self.parse_object_name(allow_unquoted_hyphen).ok()
7223+
// Try to parse `CREATE TABLE new (LIKE old [{INCLUDING | EXCLUDING} DEFAULTS])` or `CREATE TABLE new LIKE old`
7224+
let like = if self.dialect.supports_create_table_like_in_parens()
7225+
&& self.consume_token(&Token::LParen)
7226+
{
7227+
if self.parse_keyword(Keyword::LIKE) {
7228+
let name = self.parse_object_name(allow_unquoted_hyphen)?;
7229+
let defaults = if self.parse_keywords(&[Keyword::INCLUDING, Keyword::DEFAULTS]) {
7230+
Some(CreateTableLikeDefaults::Including)
7231+
} else if self.parse_keywords(&[Keyword::EXCLUDING, Keyword::DEFAULTS]) {
7232+
Some(CreateTableLikeDefaults::Excluding)
7233+
} else {
7234+
None
7235+
};
7236+
self.expect_token(&Token::RParen)?;
7237+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
7238+
name,
7239+
defaults,
7240+
}))
7241+
} else {
7242+
// Rollback the '(' it's probably the columns list
7243+
self.prev_token();
7244+
None
7245+
}
7246+
} else if self.parse_keyword(Keyword::LIKE) || self.parse_keyword(Keyword::ILIKE) {
7247+
let name = self.parse_object_name(allow_unquoted_hyphen)?;
7248+
Some(CreateTableLikeKind::NotParenthesized(CreateTableLike {
7249+
name,
7250+
defaults: None,
7251+
}))
72257252
} else {
72267253
None
72277254
};

tests/sqlparser_common.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16256,3 +16256,77 @@ fn parse_notnull() {
1625616256
// for unsupported dialects, parsing should stop at `NOT NULL`
1625716257
notnull_unsupported_dialects.expr_parses_to("NOT NULL NOTNULL", "NOT NULL");
1625816258
}
16259+
16260+
#[test]
16261+
fn parse_create_table_like() {
16262+
let dialects = all_dialects_except(|d| d.supports_create_table_like_in_parens());
16263+
let sql = "CREATE TABLE new LIKE old";
16264+
match dialects.verified_stmt(sql) {
16265+
Statement::CreateTable(stmt) => {
16266+
assert_eq!(
16267+
stmt.name,
16268+
ObjectName::from(vec![Ident::new("new".to_string())])
16269+
);
16270+
assert_eq!(
16271+
stmt.like,
16272+
Some(CreateTableLikeKind::NotParenthesized(CreateTableLike {
16273+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16274+
defaults: None,
16275+
}))
16276+
)
16277+
}
16278+
_ => unreachable!(),
16279+
}
16280+
let dialects = all_dialects_where(|d| d.supports_create_table_like_in_parens());
16281+
let sql = "CREATE TABLE new (LIKE old)";
16282+
match dialects.verified_stmt(sql) {
16283+
Statement::CreateTable(stmt) => {
16284+
assert_eq!(
16285+
stmt.name,
16286+
ObjectName::from(vec![Ident::new("new".to_string())])
16287+
);
16288+
assert_eq!(
16289+
stmt.like,
16290+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
16291+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16292+
defaults: None,
16293+
}))
16294+
)
16295+
}
16296+
_ => unreachable!(),
16297+
}
16298+
let sql = "CREATE TABLE new (LIKE old INCLUDING DEFAULTS)";
16299+
match dialects.verified_stmt(sql) {
16300+
Statement::CreateTable(stmt) => {
16301+
assert_eq!(
16302+
stmt.name,
16303+
ObjectName::from(vec![Ident::new("new".to_string())])
16304+
);
16305+
assert_eq!(
16306+
stmt.like,
16307+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
16308+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16309+
defaults: Some(CreateTableLikeDefaults::Including),
16310+
}))
16311+
)
16312+
}
16313+
_ => unreachable!(),
16314+
}
16315+
let sql = "CREATE TABLE new (LIKE old EXCLUDING DEFAULTS)";
16316+
match dialects.verified_stmt(sql) {
16317+
Statement::CreateTable(stmt) => {
16318+
assert_eq!(
16319+
stmt.name,
16320+
ObjectName::from(vec![Ident::new("new".to_string())])
16321+
);
16322+
assert_eq!(
16323+
stmt.like,
16324+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
16325+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16326+
defaults: Some(CreateTableLikeDefaults::Excluding),
16327+
}))
16328+
)
16329+
}
16330+
_ => unreachable!(),
16331+
}
16332+
}

0 commit comments

Comments
 (0)