Repository: tobymao/sqlglot

Issue:
Add support for postgres json operators
I have a sql query that has looks like below:

```
query = """
SELECT
user_id,
json_array_elements(badge_state::json->'badges')->>'handle' AS badge_name,
(json_array_elements(app_user_badges.badge_state::json->'badges')->>'progress')::integer
    AS badge_progress,
FROM
app_user_badges
"""
```

when I try to parse it using parse_one(query) command I get the following error:

 ```
raise ParseError(concat_errors(self.errors, self.max_errors))
sqlglot.errors.ParseError: Expecting ). Line 4, Col: 38.
  
SELECT
user_id,
json_array_elements(badge_state::json->'badges')->>'handle' AS badge_name,
(json_array_elements(app_user_badges.badge_state::json->'badges'

Invalid expression / Unexpected token. Line 4, Col: 48.
  
SELECT
user_id,
json_array_elements(badge_state::json->'badges')->>'handle' AS badge_name,
(json_array_elements(app_user_badges.badge_state::json->'badges')->>'prog

```
Looks like the -> operator is not a supported syntax . The query is valid, I have run it against a postgres db.

[Here is a link to json functions and operators supported by postgres](https://www.postgresql.org/docs/9.3/functions-json.html)


Discussion:
-> is supported syntax, just not after a cast to json
this will be fixed later today, but for now you can work around it by wrapping it with (x::json)->'x'

Resolving patch:
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -296,6 +296,11 @@ class Parser:
 
     COLUMN_OPERATORS = {
         TokenType.DOT: None,
+        TokenType.DCOLON: lambda self, this, to: self.expression(
+            exp.Cast,
+            this=this,
+            to=to,
+        ),
         TokenType.ARROW: lambda self, this, path: self.expression(
             exp.JSONExtract,
             this=this,
@@ -1643,12 +1648,6 @@ def _parse_type(self):
                 return self._parse_column()
             return type_token
 
-        while self._match(TokenType.DCOLON):
-            type_token = self._parse_types()
-            if not type_token:
-                self.raise_error("Expected type")
-            this = self.expression(exp.Cast, this=this, to=type_token)
-
         return this
 
     def _parse_types(self):
@@ -1738,8 +1737,15 @@ def _parse_column(self):
         this = self._parse_bracket(this)
 
         while self._match_set(self.COLUMN_OPERATORS):
-            op = self.COLUMN_OPERATORS.get(self._prev.token_type)
-            field = self._parse_star() or self._parse_function() or self._parse_id_var()
+            op_token = self._prev.token_type
+            op = self.COLUMN_OPERATORS.get(op_token)
+
+            if op_token == TokenType.DCOLON:
+                field = self._parse_types()
+                if not field:
+                    self.raise_error("Expected type")
+            else:
+                field = self._parse_star() or self._parse_function() or self._parse_id_var()
 
             if isinstance(field, exp.Func):
                 # bigquery allows function calls like x.y.count(...)
@@ -1748,7 +1754,10 @@ def _parse_column(self):
                 this = self._replace_columns_with_dots(this)
 
             if op:
-                this = op(self, this, exp.Literal.string(field.name))
+                if op_token != TokenType.DCOLON:
+                    field = exp.Literal.string(field.name)
+
+                this = op(self, this, field)
             elif isinstance(this, exp.Column) and not this.table:
                 this = self.expression(exp.Column, this=field, table=this.this)
             else:


Test patch:
diff --git a/tests/dialects/test_postgres.py b/tests/dialects/test_postgres.py
--- a/tests/dialects/test_postgres.py
+++ b/tests/dialects/test_postgres.py
@@ -172,3 +172,34 @@ def test_postgres(self):
             "SELECT id, email, CAST(deleted AS TEXT) FROM users WHERE NOT deleted IS NULL",
             read={"postgres": "SELECT id, email, CAST(deleted AS TEXT) FROM users WHERE NOT deleted ISNULL"},
         )
+        self.validate_all(
+            "'[1,2,3]'::json->2",
+            write={"postgres": "CAST('[1,2,3]' AS JSON)->'2'"},
+        )
+        self.validate_all(
+            """'{"a":1,"b":2}'::json->'b'""",
+            write={"postgres": """CAST('{"a":1,"b":2}' AS JSON)->'b'"""},
+        )
+        self.validate_all(
+            """'{"x": {"y": 1}}'::json->'x'->'y'""", write={"postgres": """CAST('{"x": {"y": 1}}' AS JSON)->'x'->'y'"""}
+        )
+        self.validate_all(
+            """'{"x": {"y": 1}}'::json->'x'::json->'y'""",
+            write={"postgres": """CAST(CAST('{"x": {"y": 1}}' AS JSON)->'x' AS JSON)->'y'"""},
+        )
+        self.validate_all(
+            """'[1,2,3]'::json->>2""",
+            write={"postgres": "CAST('[1,2,3]' AS JSON)->>'2'"},
+        )
+        self.validate_all(
+            """'{"a":1,"b":2}'::json->>'b'""",
+            write={"postgres": """CAST('{"a":1,"b":2}' AS JSON)->>'b'"""},
+        )
+        self.validate_all(
+            """'{"a":[1,2,3],"b":[4,5,6]}'::json#>'{a,2}'""",
+            write={"postgres": """CAST('{"a":[1,2,3],"b":[4,5,6]}' AS JSON)#>'{a,2}'"""},
+        )
+        self.validate_all(
+            """'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'""",
+            write={"postgres": """CAST('{"a":[1,2,3],"b":[4,5,6]}' AS JSON)#>>'{a,2}'"""},
+        )