Repository: tobymao/sqlglot

Issue:
bug: Databricks COMPUTED column is parsed as a `GENERATEDASIDENTITYCOLUMNCONSTRAINT`
Databricks COMPUTED column is parsed as a `GENERATEDASIDENTITYCOLUMNCONSTRAINT` causing computed column to be rendered as IDENTITY column

**Fully reproducible code snippet**
```python
>>> import sqlglot

>>> sqlglot.parse_one('CREATE TABLE foo (x INT GENERATED ALWAYS AS (YEAR(y)))',read='databricks')
(CREATE this: 
  (SCHEMA this: 
    (TABLE this: 
      (IDENTIFIER this: foo, quoted: False)), expressions: 
    (COLUMNDEF this: 
      (IDENTIFIER this: x, quoted: False), kind: 
      (DATATYPE this: Type.INT, nested: False, prefix: False), constraints: 
      (COLUMNCONSTRAINT kind: 
        (GENERATEDASIDENTITYCOLUMNCONSTRAINT this: True, expression: 
          (YEAR this: 
            (TSORDSTODATE this: 
              (COLUMN this: 
                (IDENTIFIER this: y, quoted: False)))))))), kind: TABLE, exists: False)


>>> sqlglot.transpile('CREATE TABLE foo (x INT GENERATED ALWAYS AS (YEAR(y)))', read='databricks',write='tsql')
['CREATE TABLE foo (x INTEGER IDENTITY(1, 1))']
```

**Official Documentation**
https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-ddl-create-table-using.html

```
{ { [CREATE OR] REPLACE TABLE | CREATE [EXTERNAL] TABLE [ IF NOT EXISTS ] }
  table_name
  [ table_specification ]
  [ USING data_source ]
  [ table_clauses ]
  [ AS query ] }

table_specification
  ( { column_identifier column_type [ NOT NULL ]
      [ GENERATED ALWAYS AS ( expr ) |
        GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( [ START WITH start ] [ INCREMENT BY step ] ) ] |
        DEFAULT default_expression ]
      [ COMMENT column_comment ]
      [ column_constraint ] } [, ...]
    [ , table_constraint ] [...] )

table_clauses
  { OPTIONS clause |
    PARTITIONED BY clause |
    clustered_by_clause |
    LOCATION path [ WITH ( CREDENTIAL credential_name ) ] |
    COMMENT table_comment |
    TBLPROPERTIES clause } [...]

clustered_by_clause
  { CLUSTERED BY ( cluster_column [, ...] )
    [ SORTED BY ( { sort_column [ ASC | DESC ] } [, ...] ) ]
    INTO num_buckets BUCKETS }
```


Discussion:

Patch:
diff --git a/sqlglot/dialects/spark.py b/sqlglot/dialects/spark.py
index 9d4a1abe..2eaa2ae9 100644
--- a/sqlglot/dialects/spark.py
+++ b/sqlglot/dialects/spark.py
@@ -54,6 +54,14 @@ class Spark(Spark2):
         FUNCTION_PARSERS = Spark2.Parser.FUNCTION_PARSERS.copy()
         FUNCTION_PARSERS.pop("ANY_VALUE")
 
+        def _parse_generated_as_identity(
+            self,
+        ) -> exp.GeneratedAsIdentityColumnConstraint | exp.ComputedColumnConstraint:
+            this = super()._parse_generated_as_identity()
+            if this.expression:
+                return self.expression(exp.ComputedColumnConstraint, this=this.expression)
+            return this
+
     class Generator(Spark2.Generator):
         TYPE_MAPPING = {
             **Spark2.Generator.TYPE_MAPPING,
@@ -73,6 +81,9 @@ class Spark(Spark2):
         TRANSFORMS.pop(exp.DateDiff)
         TRANSFORMS.pop(exp.Group)
 
+        def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
+            return f"GENERATED ALWAYS AS ({self.sql(expression, 'this')})"
+
         def anyvalue_sql(self, expression: exp.AnyValue) -> str:
             return self.function_fallback_sql(expression)
 
diff --git a/sqlglot/dialects/tsql.py b/sqlglot/dialects/tsql.py
index fa62e789..48017fe1 100644
--- a/sqlglot/dialects/tsql.py
+++ b/sqlglot/dialects/tsql.py
@@ -14,6 +14,7 @@ from sqlglot.dialects.dialect import (
     parse_date_delta,
     rename_func,
     timestrtotime_sql,
+    ts_or_ds_to_date_sql,
 )
 from sqlglot.expressions import DataType
 from sqlglot.helper import seq_get
@@ -590,6 +591,7 @@ class TSQL(Dialect):
         NVL2_SUPPORTED = False
         ALTER_TABLE_ADD_COLUMN_KEYWORD = False
         LIMIT_FETCH = "FETCH"
+        COMPUTED_COLUMN_WITH_TYPE = False
 
         TYPE_MAPPING = {
             **generator.Generator.TYPE_MAPPING,
@@ -630,6 +632,7 @@ class TSQL(Dialect):
             exp.TemporaryProperty: lambda self, e: "",
             exp.TimeStrToTime: timestrtotime_sql,
             exp.TimeToStr: _format_sql,
+            exp.TsOrDsToDate: ts_or_ds_to_date_sql("tsql"),
         }
 
         TRANSFORMS.pop(exp.ReturnsProperty)
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index eb3ac434..20049ba2 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -207,6 +207,9 @@ class Generator:
     # The keyword used to clone a table in a DDL statement
     CLONE_KEYWORD = "CLONE"
 
+    # Whether or not to include the type of a computed column in the CREATE DDL
+    COMPUTED_COLUMN_WITH_TYPE = True
+
     TYPE_MAPPING = {
         exp.DataType.Type.NCHAR: "CHAR",
         exp.DataType.Type.NVARCHAR: "VARCHAR",
@@ -652,6 +655,9 @@ class Generator:
         position = self.sql(expression, "position")
         position = f" {position}" if position else ""
 
+        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
+            kind = ""
+
         return f"{exists}{column}{kind}{constraints}{position}"
 
     def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 7b12fa76..7d9198fb 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -3761,7 +3761,9 @@ class Parser(metaclass=_Parser):
 
         return self.expression(exp.CompressColumnConstraint, this=self._parse_bitwise())
 
-    def _parse_generated_as_identity(self) -> exp.GeneratedAsIdentityColumnConstraint:
+    def _parse_generated_as_identity(
+        self,
+    ) -> exp.GeneratedAsIdentityColumnConstraint | exp.ComputedColumnConstraint:
         if self._match_text_seq("BY", "DEFAULT"):
             on_null = self._match_pair(TokenType.ON, TokenType.NULL)
             this = self.expression(


Test patch:
diff --git a/tests/dialects/test_databricks.py b/tests/dialects/test_databricks.py
index 3df968b0..21382000 100644
--- a/tests/dialects/test_databricks.py
+++ b/tests/dialects/test_databricks.py
@@ -32,6 +32,7 @@ class TestDatabricks(Validator):
             "CREATE TABLE foo (x INT GENERATED ALWAYS AS (YEAR(y)))",
             write={
                 "databricks": "CREATE TABLE foo (x INT GENERATED ALWAYS AS (YEAR(TO_DATE(y))))",
+                "tsql": "CREATE TABLE foo (x AS YEAR(CAST(y AS DATE)))",
             },
         )