Skip to content

Commit 174837d

Browse files
yoavcloudayman-sigma
authored andcommitted
Redshift: CREATE TABLE ... (LIKE ..) (apache#1967)
1 parent 7b8a32e commit 174837d

File tree

10 files changed

+224
-29
lines changed

10 files changed

+224
-29
lines changed

src/ast/ddl.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ use sqlparser_derive::{Visit, VisitMut};
3131
use crate::ast::value::escape_single_quote_string;
3232
use crate::ast::{
3333
display_comma_separated, display_separated, ArgMode, CommentDef, CreateFunctionBody,
34-
CreateFunctionUsing, CreateTableOptions, DataType, Expr, FileFormat, FunctionBehavior,
35-
FunctionCalledOnNull, FunctionDeterminismSpecifier, FunctionParallel, HiveDistributionStyle,
36-
HiveFormat, HiveIOFormat, HiveRowFormat, Ident, MySQLColumnPosition, ObjectName, OnCommit,
37-
OneOrManyWithParens, OperateFunctionArg, OrderByExpr, ProjectionSelect, Query, RowAccessPolicy,
38-
SequenceOptions, Spanned, SqlOption, StorageSerializationPolicy, Tag, Value, ValueWithSpan,
39-
WrappedCollection,
34+
CreateFunctionUsing, CreateTableLikeKind, CreateTableOptions, DataType, Expr, FileFormat,
35+
FunctionBehavior, FunctionCalledOnNull, FunctionDeterminismSpecifier, FunctionParallel,
36+
HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident, MySQLColumnPosition,
37+
ObjectName, OnCommit, OneOrManyWithParens, OperateFunctionArg, OrderByExpr, ProjectionSelect,
38+
Query, RowAccessPolicy, SequenceOptions, Spanned, SqlOption, StorageSerializationPolicy, Tag,
39+
Value, ValueWithSpan, WrappedCollection,
4040
};
4141
use crate::display_utils::{DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline};
4242
use crate::keywords::Keyword;
@@ -2430,7 +2430,7 @@ pub struct CreateTable {
24302430
pub location: Option<String>,
24312431
pub query: Option<Box<Query>>,
24322432
pub without_rowid: bool,
2433-
pub like: Option<ObjectName>,
2433+
pub like: Option<CreateTableLikeKind>,
24342434
pub clone: Option<ObjectName>,
24352435
// For Hive dialect, the table comment is after the column definitions without `=`,
24362436
// so the `comment` field is optional and different than the comment field in the general options list.
@@ -2559,6 +2559,8 @@ impl fmt::Display for CreateTable {
25592559
} else if self.query.is_none() && self.like.is_none() && self.clone.is_none() {
25602560
// PostgreSQL allows `CREATE TABLE t ();`, but requires empty parens
25612561
f.write_str(" ()")?;
2562+
} else if let Some(CreateTableLikeKind::Parenthesized(like_in_columns_list)) = &self.like {
2563+
write!(f, " ({like_in_columns_list})")?;
25622564
}
25632565

25642566
// Hive table comment should be after column definitions, please refer to:
@@ -2572,9 +2574,8 @@ impl fmt::Display for CreateTable {
25722574
write!(f, " WITHOUT ROWID")?;
25732575
}
25742576

2575-
// Only for Hive
2576-
if let Some(l) = &self.like {
2577-
write!(f, " LIKE {l}")?;
2577+
if let Some(CreateTableLikeKind::Plain(like)) = &self.like {
2578+
write!(f, " {like}")?;
25782579
}
25792580

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

src/ast/helpers/stmt_create_table.rs

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

2727
use crate::ast::{
28-
ClusteredBy, ColumnDef, CommentDef, CreateTable, CreateTableOptions, Expr, FileFormat,
29-
HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit, OneOrManyWithParens, Query,
30-
RowAccessPolicy, Statement, StorageSerializationPolicy, TableConstraint, Tag,
31-
WrappedCollection,
28+
ClusteredBy, ColumnDef, CommentDef, CreateTable, CreateTableLikeKind, CreateTableOptions, Expr,
29+
FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit,
30+
OneOrManyWithParens, Query, RowAccessPolicy, Statement, StorageSerializationPolicy,
31+
TableConstraint, Tag, WrappedCollection,
3232
};
3333

3434
use crate::parser::ParserError;
@@ -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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10494,6 +10494,62 @@ impl fmt::Display for CreateUser {
1049410494
}
1049510495
}
1049610496

10497+
/// Specifies how to create a new table based on an existing table's schema.
10498+
/// '''sql
10499+
/// CREATE TABLE new LIKE old ...
10500+
/// '''
10501+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10502+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10503+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10504+
pub enum CreateTableLikeKind {
10505+
/// '''sql
10506+
/// CREATE TABLE new (LIKE old ...)
10507+
/// '''
10508+
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html)
10509+
Parenthesized(CreateTableLike),
10510+
/// '''sql
10511+
/// CREATE TABLE new LIKE old ...
10512+
/// '''
10513+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-table#label-create-table-like)
10514+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_like)
10515+
Plain(CreateTableLike),
10516+
}
10517+
10518+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10519+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10520+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10521+
pub enum CreateTableLikeDefaults {
10522+
Including,
10523+
Excluding,
10524+
}
10525+
10526+
impl fmt::Display for CreateTableLikeDefaults {
10527+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
10528+
match self {
10529+
CreateTableLikeDefaults::Including => write!(f, "INCLUDING DEFAULTS"),
10530+
CreateTableLikeDefaults::Excluding => write!(f, "EXCLUDING DEFAULTS"),
10531+
}
10532+
}
10533+
}
10534+
10535+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10536+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10537+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10538+
pub struct CreateTableLike {
10539+
pub name: ObjectName,
10540+
pub defaults: Option<CreateTableLikeDefaults>,
10541+
}
10542+
10543+
impl fmt::Display for CreateTableLike {
10544+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
10545+
write!(f, "LIKE {}", self.name)?;
10546+
if let Some(defaults) = &self.defaults {
10547+
write!(f, " {defaults}")?;
10548+
}
10549+
Ok(())
10550+
}
10551+
}
10552+
1049710553
#[cfg(test)]
1049810554
mod tests {
1049910555
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
@@ -1168,6 +1168,25 @@ pub trait Dialect: Debug + Any {
11681168
fn supports_interval_options(&self) -> bool {
11691169
false
11701170
}
1171+
1172+
/// Returns true if the dialect supports specifying which table to copy
1173+
/// the schema from inside parenthesis.
1174+
///
1175+
/// Not parenthesized:
1176+
/// '''sql
1177+
/// CREATE TABLE new LIKE old ...
1178+
/// '''
1179+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-table#label-create-table-like)
1180+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_like)
1181+
///
1182+
/// Parenthesized:
1183+
/// '''sql
1184+
/// CREATE TABLE new (LIKE old ...)
1185+
/// '''
1186+
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html)
1187+
fn supports_create_table_like_parenthesized(&self) -> bool {
1188+
false
1189+
}
11711190
}
11721191

11731192
/// 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_parenthesized(&self) -> bool {
144+
true
145+
}
142146
}

src/dialect/snowflake.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ use crate::ast::helpers::stmt_data_loading::{
2727
};
2828
use crate::ast::{
2929
CatalogSyncNamespaceMode, ColumnOption, ColumnPolicy, ColumnPolicyProperty, ContactEntry,
30-
CopyIntoSnowflakeKind, DollarQuotedString, Ident, IdentityParameters, IdentityProperty,
31-
IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, ObjectName,
32-
ObjectNamePart, RowAccessPolicy, ShowObjects, SqlOption, Statement, StorageSerializationPolicy,
33-
TagsColumnOption, WrappedCollection,
30+
CopyIntoSnowflakeKind, CreateTableLikeKind, DollarQuotedString, Ident, IdentityParameters,
31+
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
32+
ObjectName, ObjectNamePart, RowAccessPolicy, ShowObjects, SqlOption, Statement,
33+
StorageSerializationPolicy, TagsColumnOption, WrappedCollection,
3434
};
3535
use crate::dialect::{Dialect, Precedence};
3636
use crate::keywords::Keyword;
@@ -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::Plain(
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: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7378,11 +7378,7 @@ impl<'a> Parser<'a> {
73787378
// Clickhouse has `ON CLUSTER 'cluster'` syntax for DDLs
73797379
let on_cluster = self.parse_optional_on_cluster()?;
73807380

7381-
let like = if self.parse_keyword(Keyword::LIKE) || self.parse_keyword(Keyword::ILIKE) {
7382-
self.parse_object_name(allow_unquoted_hyphen).ok()
7383-
} else {
7384-
None
7385-
};
7381+
let like = self.maybe_parse_create_table_like(allow_unquoted_hyphen)?;
73867382

73877383
let clone = if self.parse_keyword(Keyword::CLONE) {
73887384
self.parse_object_name(allow_unquoted_hyphen).ok()
@@ -7486,6 +7482,44 @@ impl<'a> Parser<'a> {
74867482
.build())
74877483
}
74887484

7485+
fn maybe_parse_create_table_like(
7486+
&mut self,
7487+
allow_unquoted_hyphen: bool,
7488+
) -> Result<Option<CreateTableLikeKind>, ParserError> {
7489+
let like = if self.dialect.supports_create_table_like_parenthesized()
7490+
&& self.consume_token(&Token::LParen)
7491+
{
7492+
if self.parse_keyword(Keyword::LIKE) {
7493+
let name = self.parse_object_name(allow_unquoted_hyphen)?;
7494+
let defaults = if self.parse_keywords(&[Keyword::INCLUDING, Keyword::DEFAULTS]) {
7495+
Some(CreateTableLikeDefaults::Including)
7496+
} else if self.parse_keywords(&[Keyword::EXCLUDING, Keyword::DEFAULTS]) {
7497+
Some(CreateTableLikeDefaults::Excluding)
7498+
} else {
7499+
None
7500+
};
7501+
self.expect_token(&Token::RParen)?;
7502+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
7503+
name,
7504+
defaults,
7505+
}))
7506+
} else {
7507+
// Rollback the '(' it's probably the columns list
7508+
self.prev_token();
7509+
None
7510+
}
7511+
} else if self.parse_keyword(Keyword::LIKE) || self.parse_keyword(Keyword::ILIKE) {
7512+
let name = self.parse_object_name(allow_unquoted_hyphen)?;
7513+
Some(CreateTableLikeKind::Plain(CreateTableLike {
7514+
name,
7515+
defaults: None,
7516+
}))
7517+
} else {
7518+
None
7519+
};
7520+
Ok(like)
7521+
}
7522+
74897523
pub(crate) fn parse_create_table_on_commit(&mut self) -> Result<OnCommit, ParserError> {
74907524
if self.parse_keywords(&[Keyword::DELETE, Keyword::ROWS]) {
74917525
Ok(OnCommit::DeleteRows)

tests/sqlparser_common.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16681,3 +16681,77 @@ fn test_parse_default_with_collate_column_option() {
1668116681
panic!("Expected create table statement");
1668216682
}
1668316683
}
16684+
16685+
#[test]
16686+
fn parse_create_table_like() {
16687+
let dialects = all_dialects_except(|d| d.supports_create_table_like_parenthesized());
16688+
let sql = "CREATE TABLE new LIKE old";
16689+
match dialects.verified_stmt(sql) {
16690+
Statement::CreateTable(stmt) => {
16691+
assert_eq!(
16692+
stmt.name,
16693+
ObjectName::from(vec![Ident::new("new".to_string())])
16694+
);
16695+
assert_eq!(
16696+
stmt.like,
16697+
Some(CreateTableLikeKind::Plain(CreateTableLike {
16698+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16699+
defaults: None,
16700+
}))
16701+
)
16702+
}
16703+
_ => unreachable!(),
16704+
}
16705+
let dialects = all_dialects_where(|d| d.supports_create_table_like_parenthesized());
16706+
let sql = "CREATE TABLE new (LIKE old)";
16707+
match dialects.verified_stmt(sql) {
16708+
Statement::CreateTable(stmt) => {
16709+
assert_eq!(
16710+
stmt.name,
16711+
ObjectName::from(vec![Ident::new("new".to_string())])
16712+
);
16713+
assert_eq!(
16714+
stmt.like,
16715+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
16716+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16717+
defaults: None,
16718+
}))
16719+
)
16720+
}
16721+
_ => unreachable!(),
16722+
}
16723+
let sql = "CREATE TABLE new (LIKE old INCLUDING DEFAULTS)";
16724+
match dialects.verified_stmt(sql) {
16725+
Statement::CreateTable(stmt) => {
16726+
assert_eq!(
16727+
stmt.name,
16728+
ObjectName::from(vec![Ident::new("new".to_string())])
16729+
);
16730+
assert_eq!(
16731+
stmt.like,
16732+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
16733+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16734+
defaults: Some(CreateTableLikeDefaults::Including),
16735+
}))
16736+
)
16737+
}
16738+
_ => unreachable!(),
16739+
}
16740+
let sql = "CREATE TABLE new (LIKE old EXCLUDING DEFAULTS)";
16741+
match dialects.verified_stmt(sql) {
16742+
Statement::CreateTable(stmt) => {
16743+
assert_eq!(
16744+
stmt.name,
16745+
ObjectName::from(vec![Ident::new("new".to_string())])
16746+
);
16747+
assert_eq!(
16748+
stmt.like,
16749+
Some(CreateTableLikeKind::Parenthesized(CreateTableLike {
16750+
name: ObjectName::from(vec![Ident::new("old".to_string())]),
16751+
defaults: Some(CreateTableLikeDefaults::Excluding),
16752+
}))
16753+
)
16754+
}
16755+
_ => unreachable!(),
16756+
}
16757+
}

0 commit comments

Comments
 (0)