Repository: tobymao/sqlglot

Issue:
Inconsistent conversion of JSON functions for DuckDB
**Problem**
Based on [the official DuckDB documentation](https://duckdb.org/docs/extensions/json.html#json-extraction-functions):
> Note that the equality comparison operator (`=`) has a higher precedence than the `->` JSON extract operator. Therefore, surround the uses of the `->` operator with parentheses when making equality comparisons.

That means `json_extract` (`->`) and `json_extract_string` (`->>`) can not be converted as is if they are surrounded by a higher priority operation.

**Fully reproducible code snippet**
```
import sqlglot
>>> print(sqlglot.parse_one("""select (json_extract_string(c, '$.k1') = 'v1') and (json_extract_string(c, '$.k2') = 'v2') from (select '{"k1": "v1", "k2": "v2"}' as c);""", read="duckdb").sql("duckdb"))
SELECT (c ->> '$.k1' = 'v1') AND (c ->> '$.k2' = 'v2') FROM (SELECT '{"k1": "v1", "k2": "v2"}' AS c)
>>> print(sqlglot.parse_one("""select json_extract_string(c, '$.k1') = 'v1' and json_extract_string(c, '$.k2') = 'v2' from (select '{"k1": "v1", "k2": "v2"}' as c);""", read="duckdb").sql("duckdb"))
SELECT c ->> '$.k1' = 'v1' AND c ->> '$.k2' = 'v2' FROM (SELECT '{"k1": "v1", "k2": "v2"}' AS c)
```

DuckDB:
```
D SELECT (c ->> '$.k1' = 'v1') AND (c ->> '$.k2' = 'v2') FROM (SELECT '{"k1": "v1", "k2": "v2"}' AS c);
┌───────────────────────────────────────────────────────┐
│ (((c ->> '$.k1') = 'v1') AND ((c ->> '$.k2') = 'v2')) │
│                        boolean                        │
├───────────────────────────────────────────────────────┤
│ true                                                  │
└───────────────────────────────────────────────────────┘
D SELECT c ->> '$.k1' = 'v1' AND c ->> '$.k2' = 'v2' FROM (SELECT '{"k1": "v1", "k2": "v2"}' AS c);
Error: Conversion Error: Could not convert string '{"k1": "v1", "k2": "v2"}' to BOOL
```

**Official Documentation**
https://duckdb.org/docs/extensions/json.html#json-extraction-functions


Discussion:

Resolving patch:
diff --git a/sqlglot/dialects/duckdb.py b/sqlglot/dialects/duckdb.py
--- a/sqlglot/dialects/duckdb.py
+++ b/sqlglot/dialects/duckdb.py
@@ -5,6 +5,7 @@
 from sqlglot import exp, generator, parser, tokens, transforms
 from sqlglot.dialects.dialect import (
     Dialect,
+    JSON_EXTRACT_TYPE,
     NormalizationStrategy,
     approx_count_distinct_sql,
     arg_max_or_min_no_count,
@@ -156,6 +157,13 @@ def _unix_to_time_sql(self: DuckDB.Generator, expression: exp.UnixToTime) -> str
     return self.func("TO_TIMESTAMP", exp.Div(this=timestamp, expression=exp.func("POW", 10, scale)))
 
 
+def _arrow_json_extract_sql(self: DuckDB.Generator, expression: JSON_EXTRACT_TYPE) -> str:
+    arrow_sql = arrow_json_extract_sql(self, expression)
+    if not expression.same_parent and isinstance(expression.parent, exp.Binary):
+        arrow_sql = self.wrap(arrow_sql)
+    return arrow_sql
+
+
 class DuckDB(Dialect):
     NULL_ORDERING = "nulls_are_last"
     SUPPORTS_USER_DEFINED_TYPES = False
@@ -405,8 +413,8 @@ class Generator(generator.Generator):
             exp.IntDiv: lambda self, e: self.binary(e, "//"),
             exp.IsInf: rename_func("ISINF"),
             exp.IsNan: rename_func("ISNAN"),
-            exp.JSONExtract: arrow_json_extract_sql,
-            exp.JSONExtractScalar: arrow_json_extract_sql,
+            exp.JSONExtract: _arrow_json_extract_sql,
+            exp.JSONExtractScalar: _arrow_json_extract_sql,
             exp.JSONFormat: _json_format_sql,
             exp.LogicalOr: rename_func("BOOL_OR"),
             exp.LogicalAnd: rename_func("BOOL_AND"),


Test patch:
diff --git a/tests/dialects/test_duckdb.py b/tests/dialects/test_duckdb.py
--- a/tests/dialects/test_duckdb.py
+++ b/tests/dialects/test_duckdb.py
@@ -271,6 +271,14 @@ def test_duckdb(self):
         self.validate_identity(
             "SELECT * FROM x LEFT JOIN UNNEST(y)", "SELECT * FROM x LEFT JOIN UNNEST(y) ON TRUE"
         )
+        self.validate_identity(
+            "SELECT JSON_EXTRACT_STRING(c, '$.k1') = 'v1'",
+            "SELECT (c ->> '$.k1') = 'v1'",
+        )
+        self.validate_identity(
+            "SELECT JSON_EXTRACT(c, '$.k1') = 'v1'",
+            "SELECT (c -> '$.k1') = 'v1'",
+        )
         self.validate_identity(
             """SELECT '{"foo": [1, 2, 3]}' -> 'foo' -> 0""",
             """SELECT '{"foo": [1, 2, 3]}' -> '$.foo' -> '$[0]'""",