diff --git a/sqlglot/generators/presto.py b/sqlglot/generators/presto.py index 4f2ccdc3d6..2909585fe4 100644 --- a/sqlglot/generators/presto.py +++ b/sqlglot/generators/presto.py @@ -16,7 +16,6 @@ regexp_extract_sql, rename_func, right_to_substring_sql, - sha256_sql, strposition_sql, struct_extract_sql, timestamptrunc_sql, @@ -385,7 +384,6 @@ class PrestoGenerator(generator.Generator): exp.MD5Digest: rename_func("MD5"), exp.SHA: rename_func("SHA1"), exp.SHA1Digest: rename_func("SHA1"), - exp.SHA2: sha256_sql, exp.SHA2Digest: sha2_digest_sql, exp.Substring: rename_func("SUBSTR"), } @@ -503,6 +501,23 @@ def md5_sql(self, expression: exp.MD5) -> str: return self.func("LOWER", self.func("TO_HEX", self.func("MD5", self.sql(this)))) + def sha2_sql(self, expression: exp.SHA2) -> str: + length = expression.text("length") or "256" + if length not in ("256", "512"): + self.unsupported(f"SHA{length} is not supported in Presto") + + this = expression.this + + if not this.type: + from sqlglot.optimizer.annotate_types import annotate_types + + this = annotate_types(this, dialect=self.dialect) + + if this.is_type(*exp.DataType.TEXT_TYPES): + this = exp.Encode(this=this, charset=exp.Literal.string("utf-8")) + + return self.func("LOWER", self.func("TO_HEX", self.func(f"SHA{length}", self.sql(this)))) + def strtounix_sql(self, expression: exp.StrToUnix) -> str: # Since `TO_UNIXTIME` requires a `TIMESTAMP`, we need to parse the argument into one. # To do this, we first try to `DATE_PARSE` it, but since this can fail when there's a diff --git a/sqlglot/parsers/presto.py b/sqlglot/parsers/presto.py index b655e0e8d4..dd7c98e7d9 100644 --- a/sqlglot/parsers/presto.py +++ b/sqlglot/parsers/presto.py @@ -129,8 +129,12 @@ class PrestoParser(parser.Parser): this=seq_get(args, 0), charset=exp.Literal.string("utf-8") ), "MD5": exp.MD5Digest.from_arg_list, - "SHA256": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)), - "SHA512": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)), + "SHA256": lambda args: exp.SHA2Digest( + this=seq_get(args, 0), length=exp.Literal.number(256) + ), + "SHA512": lambda args: exp.SHA2Digest( + this=seq_get(args, 0), length=exp.Literal.number(512) + ), "WEEK": exp.WeekOfYear.from_arg_list, } diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py index 9c8ee86aee..00b1ffac73 100644 --- a/tests/dialects/test_bigquery.py +++ b/tests/dialects/test_bigquery.py @@ -1173,8 +1173,8 @@ def test_bigquery(self): "clickhouse": "SHA512(x)", "bigquery": "SHA512(x)", "spark2": "SHA2(x, 512)", - "presto": "SHA512(x)", - "trino": "SHA512(x)", + "presto": "LOWER(TO_HEX(SHA512(x)))", + "trino": "LOWER(TO_HEX(SHA512(x)))", }, ) self.validate_all( diff --git a/tests/dialects/test_exasol.py b/tests/dialects/test_exasol.py index 183e4613ea..1297baa874 100644 --- a/tests/dialects/test_exasol.py +++ b/tests/dialects/test_exasol.py @@ -754,8 +754,6 @@ def test_scalar(self): "HASH_SHA256(x)", read={ "clickhouse": "SHA256(x)", - "presto": "SHA256(x)", - "trino": "SHA256(x)", "postgres": "SHA256(x)", "duckdb": "SHA256(x)", }, @@ -765,9 +763,9 @@ def test_scalar(self): "spark2": "SHA2(x, 256)", "clickhouse": "SHA256(x)", "postgres": "SHA256(x)", - "presto": "SHA256(x)", + "presto": "LOWER(TO_HEX(SHA256(x)))", "redshift": "SHA2(x, 256)", - "trino": "SHA256(x)", + "trino": "LOWER(TO_HEX(SHA256(x)))", "duckdb": "SHA256(x)", "snowflake": "SHA2(x, 256)", }, @@ -776,16 +774,14 @@ def test_scalar(self): "HASH_SHA512(x)", read={ "clickhouse": "SHA512(x)", - "presto": "SHA512(x)", - "trino": "SHA512(x)", }, write={ "exasol": "HASH_SHA512(x)", "clickhouse": "SHA512(x)", "bigquery": "SHA512(x)", "spark2": "SHA2(x, 512)", - "presto": "SHA512(x)", - "trino": "SHA512(x)", + "presto": "LOWER(TO_HEX(SHA512(x)))", + "trino": "LOWER(TO_HEX(SHA512(x)))", }, ) self.validate_all( diff --git a/tests/dialects/test_presto.py b/tests/dialects/test_presto.py index 4cf2a940df..7dc219dd28 100644 --- a/tests/dialects/test_presto.py +++ b/tests/dialects/test_presto.py @@ -710,6 +710,47 @@ def test_presto(self): "LOWER(TO_HEX(MD5(TO_UTF8(CONCAT(CAST(x AS VARCHAR), CAST('s' AS VARCHAR))))))", ) + self.assertEqual( + exp.func("sha2", exp.cast("x", "text"), exp.Literal.number(256)).sql(dialect="presto"), + "LOWER(TO_HEX(SHA256(TO_UTF8(CAST(x AS VARCHAR)))))", + ) + + # native SHA256/SHA512 take and return VARBINARY, so they parse as + # SHA2Digest and round-trip untouched, mirroring MD5 -> MD5Digest + self.validate_all( + "SELECT SHA256(x)", + write={ + "presto": "SELECT SHA256(x)", + "trino": "SELECT SHA256(x)", + "duckdb": "SELECT UNHEX(SHA256(x))", + "bigquery": "SELECT SHA256(x)", + }, + ) + self.validate_all( + "SELECT SHA512(x)", + write={ + "presto": "SELECT SHA512(x)", + "trino": "SELECT SHA512(x)", + "bigquery": "SELECT SHA512(x)", + }, + ) + + # string-semantics SHA2 renders hex output like md5_sql does for MD5 + self.validate_all( + "SELECT SHA2(x, 256)", + write={ + "presto": "SELECT LOWER(TO_HEX(SHA256(x)))", + "trino": "SELECT LOWER(TO_HEX(SHA256(x)))", + }, + ) + self.validate_all( + "SELECT SHA2(x, 512)", + write={ + "presto": "SELECT LOWER(TO_HEX(SHA512(x)))", + "trino": "SELECT LOWER(TO_HEX(SHA512(x)))", + }, + ) + with self.assertLogs(helper_logger): self.validate_all( "SELECT COALESCE(ELEMENT_AT(MAP_FROM_ENTRIES(ARRAY[(51, '1')]), id), quantity) FROM my_table",