Repository: tobymao/sqlglot

Issue:
`REFRESH TABLE table.name` doesn't seem to parse as expected
Read: Spark

**Fully reproducible code snippet**
```python
import sqlglot
sqlglot.parse_one("REFRESH TABLE table.name", read="spark")
```
```
> (COMMAND this: REFRESH, expression:
    (LITERAL this: TABLE table.name, is_string: True))
```

I'd expect something like
```
> (COMMAND this: REFRESH TABLE, expression:
    (TABLE this:
      (IDENTIFIER this: name, quoted: False), db:
      (IDENTIFIER this: table, quoted: False))))
```

**Official Documentation**
For the first change (REFRESH vs REFRESH TABLE), seems like they are distinct statements in the dialect:
 - [REFRESH](https://spark.apache.org/docs/latest/sql-ref-syntax-aux-cache-refresh.html)
 - [REFRESH TABLE](https://spark.apache.org/docs/latest/sql-ref-syntax-aux-cache-refresh-table.html)

Discussion:

Patch:
diff --git a/sqlglot/dialects/hive.py b/sqlglot/dialects/hive.py
index 4f18e75a..b5b02f3e 100644
--- a/sqlglot/dialects/hive.py
+++ b/sqlglot/dialects/hive.py
@@ -242,10 +242,10 @@ class Hive(Dialect):
             "ADD JAR": TokenType.COMMAND,
             "ADD JARS": TokenType.COMMAND,
             "MSCK REPAIR": TokenType.COMMAND,
-            "REFRESH": TokenType.COMMAND,
-            "WITH SERDEPROPERTIES": TokenType.SERDE_PROPERTIES,
+            "REFRESH": TokenType.REFRESH,
             "TIMESTAMP AS OF": TokenType.TIMESTAMP_SNAPSHOT,
             "VERSION AS OF": TokenType.VERSION_SNAPSHOT,
+            "WITH SERDEPROPERTIES": TokenType.SERDE_PROPERTIES,
         }
 
         NUMERIC_LITERALS = {
diff --git a/sqlglot/dialects/snowflake.py b/sqlglot/dialects/snowflake.py
index 01f7512a..62b1cd0c 100644
--- a/sqlglot/dialects/snowflake.py
+++ b/sqlglot/dialects/snowflake.py
@@ -349,7 +349,7 @@ class Snowflake(Dialect):
             table: t.Optional[exp.Expression] = None
             if self._match_text_seq("@"):
                 table_name = "@"
-                while True:
+                while self._curr:
                     self._advance()
                     table_name += self._prev.text
                     if not self._match_set(self.STAGED_FILE_SINGLE_TOKENS, advance=False):
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
index 161fecd3..cc4bf3c1 100644
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -1021,6 +1021,10 @@ class Uncache(Expression):
     arg_types = {"this": True, "exists": False}
 
 
+class Refresh(Expression):
+    pass
+
+
 class DDL(Expression):
     @property
     def ctes(self):
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index 21cb70bf..b89c30ad 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -3030,6 +3030,11 @@ class Generator:
         expression_sql = self.sql(expression, "expression")
         return f"FOR {this} DO {expression_sql}"
 
+    def refresh_sql(self, expression: exp.Refresh) -> str:
+        this = self.sql(expression, "this")
+        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
+        return f"REFRESH {table}{this}"
+
     def _simplify_unless_literal(self, expression: E) -> E:
         if not isinstance(expression, exp.Literal):
             from sqlglot.optimizer.simplify import simplify
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index c4d3f62b..ba259103 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -302,6 +302,7 @@ class Parser(metaclass=_Parser):
         TokenType.RANGE,
         TokenType.RECURSIVE,
         TokenType.REFERENCES,
+        TokenType.REFRESH,
         TokenType.RIGHT,
         TokenType.ROW,
         TokenType.ROWS,
@@ -563,6 +564,7 @@ class Parser(metaclass=_Parser):
         TokenType.MERGE: lambda self: self._parse_merge(),
         TokenType.PIVOT: lambda self: self._parse_simplified_pivot(),
         TokenType.PRAGMA: lambda self: self.expression(exp.Pragma, this=self._parse_expression()),
+        TokenType.REFRESH: lambda self: self._parse_refresh(),
         TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(),
         TokenType.SET: lambda self: self._parse_set(),
         TokenType.UNCACHE: lambda self: self._parse_uncache(),
@@ -5050,6 +5052,10 @@ class Parser(metaclass=_Parser):
 
         return self.expression(exp.Commit, chain=chain)
 
+    def _parse_refresh(self) -> exp.Refresh:
+        self._match(TokenType.TABLE)
+        return self.expression(exp.Refresh, this=self._parse_string() or self._parse_table())
+
     def _parse_add_column(self) -> t.Optional[exp.Expression]:
         if not self._match_text_seq("ADD"):
             return None
diff --git a/sqlglot/tokens.py b/sqlglot/tokens.py
index 270e6301..e5557937 100644
--- a/sqlglot/tokens.py
+++ b/sqlglot/tokens.py
@@ -297,6 +297,7 @@ class TokenType(AutoName):
     QUOTE = auto()
     RANGE = auto()
     RECURSIVE = auto()
+    REFRESH = auto()
     REPLACE = auto()
     RETURNING = auto()
     REFERENCES = auto()


Test patch:
diff --git a/tests/dialects/test_snowflake.py b/tests/dialects/test_snowflake.py
index ede42f47..7452a8c9 100644
--- a/tests/dialects/test_snowflake.py
+++ b/tests/dialects/test_snowflake.py
@@ -552,6 +552,7 @@ class TestSnowflake(Validator):
             staged_file.sql(dialect="snowflake"),
         )
 
+        self.validate_identity("SELECT metadata$filename FROM @s1/")
         self.validate_identity("SELECT * FROM @~")
         self.validate_identity("SELECT * FROM @~/some/path/to/file.csv")
         self.validate_identity("SELECT * FROM @mystage")
diff --git a/tests/dialects/test_spark.py b/tests/dialects/test_spark.py
index 841a0053..97427f23 100644
--- a/tests/dialects/test_spark.py
+++ b/tests/dialects/test_spark.py
@@ -230,11 +230,18 @@ TBLPROPERTIES (
         self.assertIsInstance(expr.args.get("ignore_nulls"), exp.Boolean)
         self.assertEqual(expr.sql(dialect="spark"), "ANY_VALUE(col, TRUE)")
 
+        self.assertEqual(
+            parse_one("REFRESH TABLE t", read="spark").assert_is(exp.Refresh).sql(dialect="spark"),
+            "REFRESH TABLE t",
+        )
+
+        self.validate_identity("REFRESH 'hdfs://path/to/table'")
+        self.validate_identity("REFRESH TABLE tempDB.view1")
         self.validate_identity("SELECT CASE WHEN a = NULL THEN 1 ELSE 2 END")
         self.validate_identity("SELECT * FROM t1 SEMI JOIN t2 ON t1.x = t2.x")
         self.validate_identity("SELECT TRANSFORM(ARRAY(1, 2, 3), x -> x + 1)")
         self.validate_identity("SELECT TRANSFORM(ARRAY(1, 2, 3), (x, i) -> x + i)")
-        self.validate_identity("REFRESH table a.b.c")
+        self.validate_identity("REFRESH TABLE a.b.c")
         self.validate_identity("INTERVAL -86 days")
         self.validate_identity("SELECT UNIX_TIMESTAMP()")
         self.validate_identity("TRIM('    SparkSQL   ')")