Repository: tobymao/sqlglot

Issue:
Parse CREATE TAG (Snowflake)
Currently parsed as a Command:

```
CREATE TAG cost_center COMMENT = 'cost_center tag';
```

Would be great to be parsed so the identifier, and comment, can be located.

https://docs.snowflake.com/en/sql-reference/sql/create-tag

Discussion:

Patch:
diff --git a/sqlglot/dialects/snowflake.py b/sqlglot/dialects/snowflake.py
index ea3832db..9e76599c 100644
--- a/sqlglot/dialects/snowflake.py
+++ b/sqlglot/dialects/snowflake.py
@@ -477,6 +477,14 @@ class Snowflake(Dialect):
 
         SCHEMA_KINDS = {"OBJECTS", "TABLES", "VIEWS", "SEQUENCES", "UNIQUE KEYS", "IMPORTED KEYS"}
 
+        def _parse_create(self) -> exp.Create | exp.Command:
+            expression = super()._parse_create()
+            if isinstance(expression, exp.Create) and expression.kind == "TAG":
+                # Replace the Table node with the enclosed Identifier
+                expression.this.replace(expression.this.this)
+
+            return expression
+
         def _parse_column_ops(self, this: t.Optional[exp.Expression]) -> t.Optional[exp.Expression]:
             this = super()._parse_column_ops(this)
 
@@ -729,6 +737,7 @@ class Snowflake(Dialect):
             "SQL_DOUBLE": TokenType.DOUBLE,
             "SQL_VARCHAR": TokenType.VARCHAR,
             "STORAGE INTEGRATION": TokenType.STORAGE_INTEGRATION,
+            "TAG": TokenType.TAG,
             "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ,
             "TOP": TokenType.TOP,
         }
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
index 8c029191..b3cd4ceb 100644
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -2437,6 +2437,10 @@ class Property(Expression):
     arg_types = {"this": True, "value": True}
 
 
+class AllowedValuesProperty(Expression):
+    arg_types = {"expressions": True}
+
+
 class AlgorithmProperty(Property):
     arg_types = {"this": True}
 
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index bdfeef38..66eb23a0 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -74,6 +74,8 @@ class Generator(metaclass=_Generator):
 
     TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = {
         **JSON_PATH_PART_TRANSFORMS,
+        exp.AllowedValuesProperty: lambda self,
+        e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}",
         exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
         exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
         exp.CaseSpecificColumnConstraint: lambda _,
@@ -401,6 +403,7 @@ class Generator(metaclass=_Generator):
     NAMED_PLACEHOLDER_TOKEN = ":"
 
     PROPERTIES_LOCATION = {
+        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
         exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
         exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
         exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index f900f17e..93f67460 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -306,22 +306,23 @@ class Parser(metaclass=_Parser):
 
     DB_CREATABLES = {
         TokenType.DATABASE,
-        TokenType.SCHEMA,
-        TokenType.TABLE,
-        TokenType.VIEW,
-        TokenType.MODEL,
         TokenType.DICTIONARY,
+        TokenType.MODEL,
+        TokenType.SCHEMA,
         TokenType.SEQUENCE,
         TokenType.STORAGE_INTEGRATION,
+        TokenType.TABLE,
+        TokenType.TAG,
+        TokenType.VIEW,
     }
 
     CREATABLES = {
         TokenType.COLUMN,
         TokenType.CONSTRAINT,
+        TokenType.FOREIGN_KEY,
         TokenType.FUNCTION,
         TokenType.INDEX,
         TokenType.PROCEDURE,
-        TokenType.FOREIGN_KEY,
         *DB_CREATABLES,
     }
 
@@ -741,6 +742,9 @@ class Parser(metaclass=_Parser):
     }
 
     PROPERTY_PARSERS: t.Dict[str, t.Callable] = {
+        "ALLOWED_VALUES": lambda self: self.expression(
+            exp.AllowedValuesProperty, expressions=self._parse_csv(self._parse_primary)
+        ),
         "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty),
         "AUTO": lambda self: self._parse_auto_property(),
         "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty),
diff --git a/sqlglot/tokens.py b/sqlglot/tokens.py
index 96fc433c..adb18136 100644
--- a/sqlglot/tokens.py
+++ b/sqlglot/tokens.py
@@ -358,6 +358,7 @@ class TokenType(AutoName):
     STORAGE_INTEGRATION = auto()
     STRUCT = auto()
     TABLE_SAMPLE = auto()
+    TAG = auto()
     TEMPORARY = auto()
     TOP = auto()
     THEN = auto()


Test patch:
diff --git a/tests/dialects/test_snowflake.py b/tests/dialects/test_snowflake.py
index 6e532bec..58eece32 100644
--- a/tests/dialects/test_snowflake.py
+++ b/tests/dialects/test_snowflake.py
@@ -1210,6 +1210,10 @@ WHERE
         self.validate_identity("CREATE SCHEMA mytestschema_clone CLONE testschema")
         self.validate_identity("CREATE TABLE IDENTIFIER('foo') (COLUMN1 VARCHAR, COLUMN2 VARCHAR)")
         self.validate_identity("CREATE TABLE IDENTIFIER($foo) (col1 VARCHAR, col2 VARCHAR)")
+        self.validate_identity("CREATE TAG cost_center ALLOWED_VALUES 'a', 'b'")
+        self.validate_identity(
+            "CREATE OR REPLACE TAG IF NOT EXISTS cost_center COMMENT='cost_center tag'"
+        ).this.assert_is(exp.Identifier)
         self.validate_identity(
             "DROP FUNCTION my_udf (OBJECT(city VARCHAR, zipcode DECIMAL, val ARRAY(BOOLEAN)))"
         )