Repository: tobymao/sqlglot

Issue:
Support FINAL/RUNNING specifier for measures in match_recognize (Snowflake)
**Fully reproducible code snippet**
```
query = """
SELECT company, price_date, price, "FINAL FIRST(LT45.price)", "FINAL LAST(LT45.price)"
    FROM stock_price_history
       MATCH_RECOGNIZE (
           MEASURES
               FINAL FIRST(LT45.price) AS "FINAL FIRST(LT45.price)",
               FINAL LAST(LT45.price)  AS "FINAL LAST(LT45.price)"
           ALL ROWS PER MATCH
           AFTER MATCH SKIP PAST LAST ROW
           )"""

output = sqlglot.parse(sql=query, dialect=sqlglot.Dialects.SNOWFLAKE)
```
You would expect it to run successfully (a `ParseError` is thrown)
```
ParseError: Expecting ). Line 6, Col: 13.
     FROM stock_price_history
       MATCH_RECOGNIZE (
           MEASURES
                FINAL FIRST(LT45.price) AS "FINAL FIRST(LT45.price)",
                LAST(LT45.price)  AS "FINAL LAST(LT45.pric
```

After removing the `FINAL` specifier - the query parsing succeed


**Official Documentation**
Snowflake documentation: https://docs.snowflake.com/en/sql-reference/constructs/match_recognize
The code snippet is based on the last code example in this docs


Discussion:

Resolving patch:
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -2225,6 +2225,13 @@ class Lateral(UDTF):
     }
 
 
+class MatchRecognizeMeasure(Expression):
+    arg_types = {
+        "this": True,
+        "window_frame": False,
+    }
+
+
 class MatchRecognize(Expression):
     arg_types = {
         "partition_by": False,
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -2111,6 +2111,14 @@ def ordered_sql(self, expression: exp.Ordered) -> str:
 
         return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
 
+    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
+        window_frame = self.sql(expression, "window_frame")
+        window_frame = f"{window_frame} " if window_frame else ""
+
+        this = self.sql(expression, "this")
+
+        return f"{window_frame}{this}"
+
     def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
         partition = self.partition_by_sql(expression)
         order = self.sql(expression, "order")
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -2738,6 +2738,13 @@ def _parse_from(
             exp.From, comments=self._prev_comments, this=self._parse_table(joins=joins)
         )
 
+    def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure:
+        return self.expression(
+            exp.MatchRecognizeMeasure,
+            window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(),
+            this=self._parse_expression(),
+        )
+
     def _parse_match_recognize(self) -> t.Optional[exp.MatchRecognize]:
         if not self._match(TokenType.MATCH_RECOGNIZE):
             return None
@@ -2746,7 +2753,12 @@ def _parse_match_recognize(self) -> t.Optional[exp.MatchRecognize]:
 
         partition = self._parse_partition_by()
         order = self._parse_order()
-        measures = self._parse_expressions() if self._match_text_seq("MEASURES") else None
+
+        measures = (
+            self._parse_csv(self._parse_match_recognize_measure)
+            if self._match_text_seq("MEASURES")
+            else None
+        )
 
         if self._match_text_seq("ONE", "ROW", "PER", "MATCH"):
             rows = exp.var("ONE ROW PER MATCH")


Test patch:
diff --git a/tests/dialects/test_snowflake.py b/tests/dialects/test_snowflake.py
--- a/tests/dialects/test_snowflake.py
+++ b/tests/dialects/test_snowflake.py
@@ -1575,22 +1575,26 @@ def test_regexp_replace(self, logger):
         )
 
     def test_match_recognize(self):
-        for row in (
-            "ONE ROW PER MATCH",
-            "ALL ROWS PER MATCH",
-            "ALL ROWS PER MATCH SHOW EMPTY MATCHES",
-            "ALL ROWS PER MATCH OMIT EMPTY MATCHES",
-            "ALL ROWS PER MATCH WITH UNMATCHED ROWS",
-        ):
-            for after in (
-                "AFTER MATCH SKIP",
-                "AFTER MATCH SKIP PAST LAST ROW",
-                "AFTER MATCH SKIP TO NEXT ROW",
-                "AFTER MATCH SKIP TO FIRST x",
-                "AFTER MATCH SKIP TO LAST x",
+        for window_frame in ("", "FINAL ", "RUNNING "):
+            for row in (
+                "ONE ROW PER MATCH",
+                "ALL ROWS PER MATCH",
+                "ALL ROWS PER MATCH SHOW EMPTY MATCHES",
+                "ALL ROWS PER MATCH OMIT EMPTY MATCHES",
+                "ALL ROWS PER MATCH WITH UNMATCHED ROWS",
             ):
-                self.validate_identity(
-                    f"""SELECT
+                for after in (
+                    "AFTER MATCH SKIP",
+                    "AFTER MATCH SKIP PAST LAST ROW",
+                    "AFTER MATCH SKIP TO NEXT ROW",
+                    "AFTER MATCH SKIP TO FIRST x",
+                    "AFTER MATCH SKIP TO LAST x",
+                ):
+                    with self.subTest(
+                        f"MATCH_RECOGNIZE with window frame {window_frame}, rows {row}, after {after}: "
+                    ):
+                        self.validate_identity(
+                            f"""SELECT
   *
 FROM x
 MATCH_RECOGNIZE (
@@ -1598,15 +1602,15 @@ def test_match_recognize(self):
   ORDER BY
     x DESC
   MEASURES
-    y AS b
+    {window_frame}y AS b
   {row}
   {after}
   PATTERN (^ S1 S2*? ( {{- S3 -}} S4 )+ | PERMUTE(S1, S2){{1,2}} $)
   DEFINE
     x AS y
 )""",
-                    pretty=True,
-                )
+                            pretty=True,
+                        )
 
     def test_show_users(self):
         self.validate_identity("SHOW USERS")