Repository: tobymao/sqlglot

Issue:
Fails to produce SQL representation of transformed tree when a JSON key has a `-`
Having a dash in the key is valid but sqlglot warns against it and raises a `ValueError`

`2024-03-20 21:44:30 [ WARNING] Invalid JSON path syntax. Unexpected TokenType.DASH at index 1: en-US (dialect.py:485)`

` ValueError: Expected an Expression. Received <class 'bool'>: True`


```py
import sqlglot 

sql = """
    SELECT ("data" ->> 'en-US') AS "acat" FROM "my_table" 
    """
parsed_query = sqlglot.parse_one(sql, read="postgres")

def transformer(node):
    ### Actually doing stuff with the node but for example just returning it ###
    return node

transformed_tree = parsed_query.transform(transformer)

## Fails to parse with '-' in the json key.
modified_sql_query = transformed_tree.sql(dialect="postgres")
```

Discussion:

Patch:
diff --git a/sqlglot/dialects/dialect.py b/sqlglot/dialects/dialect.py
index 43029f02..1fe0d5c6 100644
--- a/sqlglot/dialects/dialect.py
+++ b/sqlglot/dialects/dialect.py
@@ -1019,7 +1019,7 @@ def merge_without_target_sql(self: Generator, expression: exp.Merge) -> str:
 
 
 def build_json_extract_path(
-    expr_type: t.Type[F], zero_based_indexing: bool = True
+    expr_type: t.Type[F], zero_based_indexing: bool = True, arrow_req_json_type: bool = False
 ) -> t.Callable[[t.List], F]:
     def _builder(args: t.List) -> F:
         segments: t.List[exp.JSONPathPart] = [exp.JSONPathRoot()]
@@ -1039,7 +1039,11 @@ def build_json_extract_path(
 
         # This is done to avoid failing in the expression validator due to the arg count
         del args[2:]
-        return expr_type(this=seq_get(args, 0), expression=exp.JSONPath(expressions=segments))
+        return expr_type(
+            this=seq_get(args, 0),
+            expression=exp.JSONPath(expressions=segments),
+            only_json_types=arrow_req_json_type,
+        )
 
     return _builder
 
diff --git a/sqlglot/dialects/mysql.py b/sqlglot/dialects/mysql.py
index 2836dcc5..4ea89b21 100644
--- a/sqlglot/dialects/mysql.py
+++ b/sqlglot/dialects/mysql.py
@@ -408,6 +408,11 @@ class MySQL(Dialect):
             "SPATIAL": lambda self: self._parse_index_constraint(kind="SPATIAL"),
         }
 
+        ALTER_PARSERS = {
+            **parser.Parser.ALTER_PARSERS,
+            "MODIFY": lambda self: self._parse_alter_table_alter(),
+        }
+
         SCHEMA_UNNAMED_CONSTRAINTS = {
             *parser.Parser.SCHEMA_UNNAMED_CONSTRAINTS,
             "FULLTEXT",
diff --git a/sqlglot/dialects/postgres.py b/sqlglot/dialects/postgres.py
index 35fc01ed..11398ed2 100644
--- a/sqlglot/dialects/postgres.py
+++ b/sqlglot/dialects/postgres.py
@@ -357,6 +357,16 @@ class Postgres(Dialect):
 
         JSON_ARROWS_REQUIRE_JSON_TYPE = True
 
+        COLUMN_OPERATORS = {
+            **parser.Parser.COLUMN_OPERATORS,
+            TokenType.ARROW: lambda self, this, path: build_json_extract_path(
+                exp.JSONExtract, arrow_req_json_type=self.JSON_ARROWS_REQUIRE_JSON_TYPE
+            )([this, path]),
+            TokenType.DARROW: lambda self, this, path: build_json_extract_path(
+                exp.JSONExtractScalar, arrow_req_json_type=self.JSON_ARROWS_REQUIRE_JSON_TYPE
+            )([this, path]),
+        }
+
         def _parse_operator(self, this: t.Optional[exp.Expression]) -> t.Optional[exp.Expression]:
             while True:
                 if not self._match(TokenType.L_PAREN):
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
index 57a1044f..883ab41a 100644
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -643,7 +643,9 @@ class Expression(metaclass=_Expression):
         key = self.arg_key
         value = parent.args.get(key)
 
-        if type(expression) is list and isinstance(value, Expression) and value.parent:
+        if type(expression) is list and isinstance(value, Expression):
+            # We are trying to replace an Expression with a list, so it's assumed that
+            # the intention was to really replace the parent of this expression.
             value.parent.replace(expression)
         else:
             parent.set(key, expression, self.index)
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 9102f8a7..274614af 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -5695,10 +5695,11 @@ class Parser(metaclass=_Parser):
             return self.expression(exp.AlterColumn, this=column, comment=self._parse_string())
 
         self._match_text_seq("SET", "DATA")
+        self._match_text_seq("TYPE")
         return self.expression(
             exp.AlterColumn,
             this=column,
-            dtype=self._match_text_seq("TYPE") and self._parse_types(),
+            dtype=self._parse_types(),
             collate=self._match(TokenType.COLLATE) and self._parse_term(),
             using=self._match(TokenType.USING) and self._parse_conjunction(),
         )


Test patch:
diff --git a/tests/dialects/test_mysql.py b/tests/dialects/test_mysql.py
index e1587601..23607da6 100644
--- a/tests/dialects/test_mysql.py
+++ b/tests/dialects/test_mysql.py
@@ -85,6 +85,9 @@ class TestMySQL(Validator):
             "ALTER TABLE test_table ALTER COLUMN test_column SET DATA TYPE LONGTEXT",
             "ALTER TABLE test_table MODIFY COLUMN test_column LONGTEXT",
         )
+        self.validate_identity(
+            "ALTER TABLE test_table MODIFY COLUMN test_column LONGTEXT",
+        )
         self.validate_identity(
             "CREATE TABLE t (c DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC",
             "CREATE TABLE t (c DATETIME DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()) DEFAULT CHARACTER SET=utf8 ROW_FORMAT=DYNAMIC",
diff --git a/tests/dialects/test_postgres.py b/tests/dialects/test_postgres.py
index e2a153f5..f1c17667 100644
--- a/tests/dialects/test_postgres.py
+++ b/tests/dialects/test_postgres.py
@@ -108,6 +108,8 @@ class TestPostgres(Validator):
         self.validate_identity(
             "SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) AS ss"
         )
+        self.validate_identity("SELECT ('data' -> 'en-US') AS acat FROM my_table")
+        self.validate_identity("SELECT ('data' ->> 'en-US') AS acat FROM my_table")
         self.validate_identity(
             "SELECT c.oid, n.nspname, c.relname "
             "FROM pg_catalog.pg_class AS c "