Skip to content

Commit

Permalink
Feat(snowflake): add support for the CLONE clause in DDL statements (#…
Browse files Browse the repository at this point in the history
…1627)

* Feat(snowflake): add support for the CLONE clause in DDL statements

* Leverage simplify to transpile arithmetic expressions in TO_TIMESTAMP correctly

* Use simplify_literals instead of simplify

* Convert texts to uppercase
  • Loading branch information
georgesittas authored May 15, 2023
1 parent 9c3cd2c commit 8610298
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 3 deletions.
6 changes: 5 additions & 1 deletion sqlglot/dialects/snowflake.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ def _snowflake_to_timestamp(args: t.Sequence) -> t.Union[exp.StrToTime, exp.Unix

return exp.UnixToTime(this=first_arg, scale=timescale)

from sqlglot.optimizer.simplify import simplify_literals

# The first argument might be an expression like 40 * 365 * 86400, so we try to
# reduce it using `simplify_literals` first and then check if it's a Literal.
first_arg = seq_get(args, 0)
if not isinstance(first_arg, Literal):
if not isinstance(simplify_literals(first_arg, root=True), Literal):
# case: <variant_expr>
return format_time_lambda(exp.StrToTime, "snowflake", default=True)(args)

Expand Down
11 changes: 11 additions & 0 deletions sqlglot/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,17 @@ class Create(Expression):
"indexes": False,
"no_schema_binding": False,
"begin": False,
"clone": False,
}


# https://docs.snowflake.com/en/sql-reference/sql/create-clone
class Clone(Expression):
arg_types = {
"this": True,
"when": False,
"kind": False,
"expression": False,
}


Expand Down
16 changes: 15 additions & 1 deletion sqlglot/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,9 +725,23 @@ def create_sql(self, expression: exp.Create) -> str:
" WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
)

expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}"
clone = self.sql(expression, "clone")
clone = f" {clone}" if clone else ""

expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
return self.prepend_ctes(expression, expression_sql)

def clone_sql(self, expression: exp.Clone) -> str:
this = self.sql(expression, "this")
when = self.sql(expression, "when")

if when:
kind = self.sql(expression, "kind")
expr = self.sql(expression, "expression")
return f"CLONE {this} {when} ({kind} => {expr})"

return f"CLONE {this}"

def describe_sql(self, expression: exp.Describe) -> str:
return f"DESCRIBE {self.sql(expression, 'this')}"

Expand Down
18 changes: 18 additions & 0 deletions sqlglot/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,8 @@ class Parser(metaclass=_Parser):

INSERT_ALTERNATIVES = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"}

CLONE_KINDS = {"TIMESTAMP", "OFFSET", "STATEMENT"}

WINDOW_ALIAS_TOKENS = ID_VAR_TOKENS - {TokenType.ROWS}
WINDOW_BEFORE_PAREN_TOKENS = {TokenType.OVER}

Expand Down Expand Up @@ -1152,6 +1154,7 @@ def _parse_create(self) -> t.Optional[exp.Expression]:
indexes = None
no_schema_binding = None
begin = None
clone = None

if create_token.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
this = self._parse_user_defined_function(kind=create_token.token_type)
Expand Down Expand Up @@ -1234,6 +1237,20 @@ def _parse_create(self) -> t.Optional[exp.Expression]:
if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"):
no_schema_binding = True

if self._match_text_seq("CLONE"):
clone = self._parse_table(schema=True)
when = self._match_texts({"AT", "BEFORE"}) and self._prev.text.upper()
clone_kind = (
self._match(TokenType.L_PAREN)
and self._match_texts(self.CLONE_KINDS)
and self._prev.text.upper()
)
clone_expression = self._match(TokenType.FARROW) and self._parse_bitwise()
self._match(TokenType.R_PAREN)
clone = self.expression(
exp.Clone, this=clone, when=when, kind=clone_kind, expression=clone_expression
)

return self.expression(
exp.Create,
this=this,
Expand All @@ -1246,6 +1263,7 @@ def _parse_create(self) -> t.Optional[exp.Expression]:
indexes=indexes,
no_schema_binding=no_schema_binding,
begin=begin,
clone=clone,
)

def _parse_property_before(self) -> t.Optional[exp.Expression]:
Expand Down
14 changes: 13 additions & 1 deletion tests/dialects/test_snowflake.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,10 +555,22 @@ def test_semi_structured_types(self):
)

def test_ddl(self):
self.validate_identity("CREATE MATERIALIZED VIEW a COMMENT='...' AS SELECT 1 FROM x")
self.validate_identity("CREATE DATABASE mytestdb_clone CLONE mytestdb")
self.validate_identity("CREATE SCHEMA mytestschema_clone CLONE testschema")
self.validate_identity("CREATE TABLE orders_clone CLONE orders")
self.validate_identity(
"CREATE TABLE orders_clone_restore CLONE orders AT (TIMESTAMP => TO_TIMESTAMP_TZ('04/05/2013 01:02:03', 'mm/dd/yyyy hh24:mi:ss'))"
)
self.validate_identity(
"CREATE TABLE orders_clone_restore CLONE orders BEFORE (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726')"
)
self.validate_identity(
"CREATE TABLE a (x DATE, y BIGINT) WITH (PARTITION BY (x), integration='q', auto_refresh=TRUE, file_format=(type = parquet))"
)
self.validate_identity("CREATE MATERIALIZED VIEW a COMMENT='...' AS SELECT 1 FROM x")
self.validate_identity(
"CREATE SCHEMA mytestschema_clone_restore CLONE testschema BEFORE (TIMESTAMP => TO_TIMESTAMP(40 * 365 * 86400))"
)

self.validate_all(
"CREATE OR REPLACE TRANSIENT TABLE a (id INT)",
Expand Down

0 comments on commit 8610298

Please sign in to comment.