Repository: tobymao/sqlglot

Issue:
Unicode character escape is not correctly converted from Trino
Trino uses a `U&` prefix to indicate that a string has unicode characters escaped with a `\`.
https://trino.io/docs/current/language/types.html#varchar

SparkSQL don't use a prefix but escapes unicode characters with a `\u`.
https://spark.apache.org/docs/latest/sql-ref-literals.html#parameters 

**Fully reproducible code snippet**
```
in_sql = '''select U&'n\00e3o' as no'''
out_sql = sqlglot.transpile(in_sql, read='trino', write='spark', pretty=True)[0]
print(out_sql)
```

** Output **
```
SELECT
  U & 'ne3o' AS no
```

** Expected Output **
```
SELECT 
   'n\u00e3o' AS no
```


Discussion:
@rantos9 I'll try to add support for this at least for the Trino -> Trino case, as it seems that we don't currently support it; the tokenization is invalid. After that's done, I'll take a look at the Trino -> Spark conversion, but my first guess is that it's gonna be cumbersome to do it and so we might not solve that for now. Will let you know when there's an update..

Resolving patch:
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import logging
+import re
 import typing as t
 from collections import defaultdict
 from functools import reduce
@@ -17,6 +18,8 @@
 
 logger = logging.getLogger("sqlglot")
 
+ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)")
+
 
 class Generator:
     """
@@ -917,11 +920,19 @@ def bytestring_sql(self, expression: exp.ByteString) -> str:
 
     def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
         this = self.sql(expression, "this")
+        escape = expression.args.get("escape")
+
         if self.dialect.UNICODE_START:
-            escape = self.sql(expression, "escape")
-            escape = f" UESCAPE {escape}" if escape else ""
+            escape = f" UESCAPE {self.sql(escape)}" if escape else ""
             return f"{self.dialect.UNICODE_START}{this}{self.dialect.UNICODE_END}{escape}"
-        return this
+
+        if escape:
+            pattern = re.compile(rf"{escape.name}(\d+)")
+        else:
+            pattern = ESCAPED_UNICODE_RE
+
+        this = pattern.sub(r"\\u\1", this)
+        return f"{self.dialect.QUOTE_START}{this}{self.dialect.QUOTE_END}"
 
     def rawstring_sql(self, expression: exp.RawString) -> str:
         string = self.escape_str(expression.this.replace("\\", "\\\\"))


Test patch:
diff --git a/tests/dialects/test_presto.py b/tests/dialects/test_presto.py
--- a/tests/dialects/test_presto.py
+++ b/tests/dialects/test_presto.py
@@ -546,13 +546,21 @@ def test_unnest(self):
 
     def test_unicode_string(self):
         for prefix in ("u&", "U&"):
-            self.validate_identity(
+            self.validate_all(
                 f"{prefix}'Hello winter \\2603 !'",
-                "U&'Hello winter \\2603 !'",
+                write={
+                    "presto": "U&'Hello winter \\2603 !'",
+                    "snowflake": "'Hello winter \\u2603 !'",
+                    "spark": "'Hello winter \\u2603 !'",
+                },
             )
-            self.validate_identity(
+            self.validate_all(
                 f"{prefix}'Hello winter #2603 !' UESCAPE '#'",
-                "U&'Hello winter #2603 !' UESCAPE '#'",
+                write={
+                    "presto": "U&'Hello winter #2603 !' UESCAPE '#'",
+                    "snowflake": "'Hello winter \\u2603 !'",
+                    "spark": "'Hello winter \\u2603 !'",
+                },
             )
 
     def test_presto(self):