Repository: tobymao/sqlglot

Issue:
Trino aliases cannot start with an integer if unquoted
This is a valid Spark query, but it's not in Trino, I get an error "identifiers must not start with a digit; surround the identifier with double quotes"

```
>>> import sqlglot
>>> sqlglot.__version__
'15.0.0'
>>> sqlglot.transpile("SELECT 1 AS 1x", read='spark', write='trino')[0]
'SELECT 1 AS 1x'
```

The desired output here is then

```
>>> sqlglot.transpile("SELECT 1 AS 1x", read='spark', write='trino')[0]
'SELECT 1 AS "1x"'
```

Discussion:

Patch:
diff --git a/sqlglot/dialects/dialect.py b/sqlglot/dialects/dialect.py
index 890a3c3c..448cf347 100644
--- a/sqlglot/dialects/dialect.py
+++ b/sqlglot/dialects/dialect.py
@@ -104,6 +104,10 @@ class _Dialect(type):
         klass.byte_start, klass.byte_end = get_start_end(TokenType.BYTE_STRING)
         klass.raw_start, klass.raw_end = get_start_end(TokenType.RAW_STRING)
 
+        klass.tokenizer_class.identifiers_can_start_with_digit = (
+            klass.identifiers_can_start_with_digit
+        )
+
         return klass
 
 
@@ -111,6 +115,7 @@ class Dialect(metaclass=_Dialect):
     index_offset = 0
     unnest_column_only = False
     alias_post_tablesample = False
+    identifiers_can_start_with_digit = False
     normalize_functions: t.Optional[str] = "upper"
     null_ordering = "nulls_are_small"
 
@@ -231,6 +236,7 @@ class Dialect(metaclass=_Dialect):
                 "time_trie": self.inverse_time_trie,
                 "unnest_column_only": self.unnest_column_only,
                 "alias_post_tablesample": self.alias_post_tablesample,
+                "identifiers_can_start_with_digit": self.identifiers_can_start_with_digit,
                 "normalize_functions": self.normalize_functions,
                 "null_ordering": self.null_ordering,
                 **opts,
diff --git a/sqlglot/dialects/hive.py b/sqlglot/dialects/hive.py
index fbd626a2..3f9ed008 100644
--- a/sqlglot/dialects/hive.py
+++ b/sqlglot/dialects/hive.py
@@ -149,6 +149,7 @@ def _to_date_sql(self: generator.Generator, expression: exp.TsOrDsToDate) -> str
 
 class Hive(Dialect):
     alias_post_tablesample = True
+    identifiers_can_start_with_digit = True
 
     time_mapping = {
         "y": "%Y",
@@ -190,7 +191,6 @@ class Hive(Dialect):
         IDENTIFIERS = ["`"]
         STRING_ESCAPES = ["\\"]
         ENCODE = "utf-8"
-        IDENTIFIER_CAN_START_WITH_DIGIT = True
 
         KEYWORDS = {
             **tokens.Tokenizer.KEYWORDS,
diff --git a/sqlglot/dialects/presto.py b/sqlglot/dialects/presto.py
index 52a04a44..db223d1f 100644
--- a/sqlglot/dialects/presto.py
+++ b/sqlglot/dialects/presto.py
@@ -272,6 +272,7 @@ class Presto(Dialect):
             exp.BitwiseOr: lambda self, e: f"BITWISE_OR({self.sql(e, 'this')}, {self.sql(e, 'expression')})",
             exp.BitwiseRightShift: lambda self, e: f"BITWISE_ARITHMETIC_SHIFT_RIGHT({self.sql(e, 'this')}, {self.sql(e, 'expression')})",
             exp.BitwiseXor: lambda self, e: f"BITWISE_XOR({self.sql(e, 'this')}, {self.sql(e, 'expression')})",
+            exp.Cast: transforms.preprocess([transforms.epoch_cast_to_ts]),
             exp.CurrentTimestamp: lambda *_: "CURRENT_TIMESTAMP",
             exp.DataType: _datatype_sql,
             exp.DateAdd: lambda self, e: self.func(
@@ -319,6 +320,7 @@ class Presto(Dialect):
             exp.TimeStrToUnix: lambda self, e: f"TO_UNIXTIME(DATE_PARSE({self.sql(e, 'this')}, {Presto.time_format}))",
             exp.TimeToStr: lambda self, e: f"DATE_FORMAT({self.sql(e, 'this')}, {self.format_time(e)})",
             exp.TimeToUnix: rename_func("TO_UNIXTIME"),
+            exp.TryCast: transforms.preprocess([transforms.epoch_cast_to_ts]),
             exp.TsOrDiToDi: lambda self, e: f"CAST(SUBSTR(REPLACE(CAST({self.sql(e, 'this')} AS VARCHAR), '-', ''), 1, 8) AS INT)",
             exp.TsOrDsAdd: _ts_or_ds_add_sql,
             exp.TsOrDsToDate: _ts_or_ds_to_date_sql,
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index 55dd589d..4a6e472e 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -44,6 +44,8 @@ class Generator:
             Default: "upper"
         alias_post_tablesample (bool): if the table alias comes after tablesample
             Default: False
+        identifiers_can_start_with_digit (bool): if an unquoted identifier can start with digit
+            Default: False
         unsupported_level (ErrorLevel): determines the generator's behavior when it encounters
             unsupported expressions. Default ErrorLevel.WARN.
         null_ordering (str): Indicates the default null ordering method to use if not explicitly set.
@@ -266,6 +268,7 @@ class Generator:
         "index_offset",
         "unnest_column_only",
         "alias_post_tablesample",
+        "identifiers_can_start_with_digit",
         "normalize_functions",
         "unsupported_level",
         "unsupported_messages",
@@ -306,6 +309,7 @@ class Generator:
         index_offset=0,
         unnest_column_only=False,
         alias_post_tablesample=False,
+        identifiers_can_start_with_digit=False,
         normalize_functions="upper",
         unsupported_level=ErrorLevel.WARN,
         null_ordering=None,
@@ -339,6 +343,7 @@ class Generator:
         self.index_offset = index_offset
         self.unnest_column_only = unnest_column_only
         self.alias_post_tablesample = alias_post_tablesample
+        self.identifiers_can_start_with_digit = identifiers_can_start_with_digit
         self.normalize_functions = normalize_functions
         self.unsupported_level = unsupported_level
         self.unsupported_messages = []
@@ -896,6 +901,7 @@ class Generator:
             expression.quoted
             or should_identify(text, self.identify)
             or lower in self.RESERVED_KEYWORDS
+            or (not self.identifiers_can_start_with_digit and text[:1].isdigit())
         ):
             text = f"{self.identifier_start}{text}{self.identifier_end}"
         return text
diff --git a/sqlglot/optimizer/pushdown_predicates.py b/sqlglot/optimizer/pushdown_predicates.py
index 96dda33b..b89a82b2 100644
--- a/sqlglot/optimizer/pushdown_predicates.py
+++ b/sqlglot/optimizer/pushdown_predicates.py
@@ -10,10 +10,10 @@ def pushdown_predicates(expression):
 
     Example:
         >>> import sqlglot
-        >>> sql = "SELECT * FROM (SELECT * FROM x AS x) AS y WHERE y.a = 1"
+        >>> sql = "SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y WHERE y.a = 1"
         >>> expression = sqlglot.parse_one(sql)
         >>> pushdown_predicates(expression).sql()
-        'SELECT * FROM (SELECT * FROM x AS x WHERE y.a = 1) AS y WHERE TRUE'
+        'SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x WHERE x.a = 1) AS y WHERE TRUE'
 
     Args:
         expression (sqlglot.Expression): expression to optimize
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index a21a667f..39696e9f 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -3060,11 +3060,7 @@ class Parser(metaclass=_Parser):
                     else exp.Literal.string(value)
                 )
             else:
-                field = (
-                    self._parse_star()
-                    or self._parse_function(anonymous=True)
-                    or self._parse_id_var()
-                )
+                field = self._parse_field(anonymous_func=True)
 
             if isinstance(field, exp.Func):
                 # bigquery allows function calls like x.y.count(...)
@@ -3135,10 +3131,11 @@ class Parser(metaclass=_Parser):
         self,
         any_token: bool = False,
         tokens: t.Optional[t.Collection[TokenType]] = None,
+        anonymous_func: bool = False,
     ) -> t.Optional[exp.Expression]:
         return (
             self._parse_primary()
-            or self._parse_function()
+            or self._parse_function(anonymous=anonymous_func)
             or self._parse_id_var(any_token=any_token, tokens=tokens)
         )
 
diff --git a/sqlglot/tokens.py b/sqlglot/tokens.py
index 20dfc96d..f013635e 100644
--- a/sqlglot/tokens.py
+++ b/sqlglot/tokens.py
@@ -735,8 +735,6 @@ class Tokenizer(metaclass=_Tokenizer):
     COMMENTS = ["--", ("/*", "*/"), ("{#", "#}")]
     KEYWORD_TRIE: t.Dict = {}  # autofilled
 
-    IDENTIFIER_CAN_START_WITH_DIGIT = False
-
     __slots__ = (
         "sql",
         "size",
@@ -750,6 +748,7 @@ class Tokenizer(metaclass=_Tokenizer):
         "_end",
         "_peek",
         "_prev_token_line",
+        "identifiers_can_start_with_digit",
     )
 
     def __init__(self) -> None:
@@ -1010,7 +1009,7 @@ class Tokenizer(metaclass=_Tokenizer):
                     self._add(TokenType.NUMBER, number_text)
                     self._add(TokenType.DCOLON, "::")
                     return self._add(token_type, literal)
-                elif self.IDENTIFIER_CAN_START_WITH_DIGIT:
+                elif self.identifiers_can_start_with_digit:  # type: ignore
                     return self._add(TokenType.VAR)
 
                 self._add(TokenType.NUMBER, number_text)
diff --git a/sqlglot/transforms.py b/sqlglot/transforms.py
index a1ec1bda..ba72616d 100644
--- a/sqlglot/transforms.py
+++ b/sqlglot/transforms.py
@@ -268,6 +268,17 @@ def add_recursive_cte_column_names(expression: exp.Expression) -> exp.Expression
     return expression
 
 
+def epoch_cast_to_ts(expression: exp.Expression) -> exp.Expression:
+    if (
+        isinstance(expression, (exp.Cast, exp.TryCast))
+        and expression.name.lower() == "epoch"
+        and expression.to.this in exp.DataType.TEMPORAL_TYPES
+    ):
+        expression.this.replace(exp.Literal.string("1970-01-01 00:00:00"))
+
+    return expression
+
+
 def preprocess(
     transforms: t.List[t.Callable[[exp.Expression], exp.Expression]],
 ) -> t.Callable[[Generator, exp.Expression], str]:


Test patch:
diff --git a/tests/dialects/test_hive.py b/tests/dialects/test_hive.py
index 99b5602b..f6cc2244 100644
--- a/tests/dialects/test_hive.py
+++ b/tests/dialects/test_hive.py
@@ -412,6 +412,7 @@ class TestHive(Validator):
             "SELECT 1_a AS a FROM test_table",
             write={
                 "spark": "SELECT 1_a AS a FROM test_table",
+                "trino": 'SELECT "1_a" AS a FROM test_table',
             },
         )
         self.validate_all(
diff --git a/tests/dialects/test_presto.py b/tests/dialects/test_presto.py
index 1f5953ca..5b8e0568 100644
--- a/tests/dialects/test_presto.py
+++ b/tests/dialects/test_presto.py
@@ -6,6 +6,10 @@ class TestPresto(Validator):
     dialect = "presto"
 
     def test_cast(self):
+        self.validate_all(
+            "SELECT TRY_CAST('1970-01-01 00:00:00' AS TIMESTAMP)",
+            read={"postgres": "SELECT 'epoch'::TIMESTAMP"},
+        )
         self.validate_all(
             "FROM_BASE64(x)",
             read={