From efd9b4ed5a761a2ebfc47a1582e9d1b2eb7cb277 Mon Sep 17 00:00:00 2001 From: Vaggelis Danias Date: Mon, 28 Oct 2024 22:42:18 +0200 Subject: [PATCH] feat(postgres): Support JSONB_EXISTS (#4302) --- sqlglot/dialects/duckdb.py | 1 + sqlglot/dialects/postgres.py | 9 +++++++++ sqlglot/expressions.py | 5 +++++ tests/dialects/test_postgres.py | 8 ++++++++ 4 files changed, 23 insertions(+) diff --git a/sqlglot/dialects/duckdb.py b/sqlglot/dialects/duckdb.py index 4a2460f553..391d4603be 100644 --- a/sqlglot/dialects/duckdb.py +++ b/sqlglot/dialects/duckdb.py @@ -536,6 +536,7 @@ class Generator(generator.Generator): exp.IntDiv: lambda self, e: self.binary(e, "//"), exp.IsInf: rename_func("ISINF"), exp.IsNan: rename_func("ISNAN"), + exp.JSONBExists: rename_func("JSON_EXISTS"), exp.JSONExtract: _arrow_json_extract_sql, exp.JSONExtractScalar: _arrow_json_extract_sql, exp.JSONFormat: _json_format_sql, diff --git a/sqlglot/dialects/postgres.py b/sqlglot/dialects/postgres.py index 7c431c78d3..a94bccbdb4 100644 --- a/sqlglot/dialects/postgres.py +++ b/sqlglot/dialects/postgres.py @@ -370,6 +370,7 @@ class Parser(parser.Parser): FUNCTION_PARSERS = { **parser.Parser.FUNCTION_PARSERS, "DATE_PART": lambda self: self._parse_date_part(), + "JSONB_EXISTS": lambda self: self._parse_jsonb_exists(), } BITWISE = { @@ -443,6 +444,14 @@ def _parse_date_part(self) -> exp.Expression: def _parse_unique_key(self) -> t.Optional[exp.Expression]: return None + def _parse_jsonb_exists(self) -> exp.JSONBExists: + return self.expression( + exp.JSONBExists, + this=self._parse_bitwise(), + path=self._match(TokenType.COMMA) + and self.dialect.to_json_path(self._parse_bitwise()), + ) + class Generator(generator.Generator): SINGLE_STRING_INTERVAL = True RENAME_TABLE_WITH_DB = False diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py index d1adce363e..5facee3616 100644 --- a/sqlglot/expressions.py +++ b/sqlglot/expressions.py @@ -5960,6 +5960,11 @@ class JSONBContains(Binary, Func): _sql_names = ["JSONB_CONTAINS"] +class JSONBExists(Func): + arg_types = {"this": True, "path": True} + _sql_names = ["JSONB_EXISTS"] + + class JSONExtract(Binary, Func): arg_types = { "this": True, diff --git a/tests/dialects/test_postgres.py b/tests/dialects/test_postgres.py index 62ae24729e..0f9ab3c23b 100644 --- a/tests/dialects/test_postgres.py +++ b/tests/dialects/test_postgres.py @@ -797,6 +797,14 @@ def test_postgres(self): self.validate_identity("SELECT OVERLAY(a PLACING b FROM 1 FOR 1)") self.validate_identity("ARRAY[1, 2, 3] && ARRAY[1, 2]").assert_is(exp.ArrayOverlaps) + self.validate_all( + """SELECT JSONB_EXISTS('{"a": [1,2,3]}', 'a')""", + write={ + "postgres": """SELECT JSONB_EXISTS('{"a": [1,2,3]}', 'a')""", + "duckdb": """SELECT JSON_EXISTS('{"a": [1,2,3]}', '$.a')""", + }, + ) + def test_ddl(self): # Checks that user-defined types are parsed into DataType instead of Identifier self.parse_one("CREATE TABLE t (a udt)").this.expressions[0].args["kind"].assert_is(