Repository: tobymao/sqlglot

Issue:
Support of first/last clause in oracle dialect
**Before you file an issue**
it seems there is support for only 1 window clause for an aggregate with the KEEP/OVER clause
so the following statements parse successfully.
```
SELECT last_name, department_id, salary,
       MAX(salary) KEEP (DENSE_RANK FIRST ORDER BY commission_pct)
          "Worst"
   FROM employees
   ORDER BY department_id, salary, last_name;

SELECT last_name, department_id, salary,
       MAX(salary)
         OVER (PARTITION BY department_id) "Worst"
   FROM employees
   ORDER BY department_id, salary, last_name

```
but the full first\last clause of oracle fails to parse.
would it be possible to get support for this clause?

**Fully reproducible code snippet**
```
from sqlglot import parse_one
from sqlglot.dialects.oracle import Oracle

parser = Oracle()

sql = """
SELECT last_name, department_id, salary,
       MIN(salary) KEEP (DENSE_RANK FIRST ORDER BY commission_pct)
         OVER (PARTITION BY department_id) "Worst",
       MAX(salary) KEEP (DENSE_RANK LAST ORDER BY commission_pct)
         OVER (PARTITION BY department_id) "Best"
   FROM employees
   ORDER BY department_id, salary, last_name
"""

ast = parse_one(sql, read=parser)

```

error:
```
sqlglot.errors.ParseError: Invalid expression / Unexpected token. Line 4, Col: 13.
   department_id, salary,
       MAX(salary) KEEP (DENSE_RANK FIRST ORDER BY commission_pct)
         OVER (PARTITION BY department_id) "Worst",
       MAX(salary) KEEP (DENSE_RANK LAST ORDER BY commission_
```

**Official Documentation**
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/FIRST.html#GUID-85AB9246-0E0A-44A1-A7E6-4E57502E9238
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/LAST.html#GUID-4E16BC0E-D3B8-4BA4-8F97-3A08891A85CC


Discussion:

Patch:
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 5adec776..fcb54d1b 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -4172,7 +4172,7 @@ class Parser(metaclass=_Parser):
 
         self._match_r_paren()
 
-        return self.expression(
+        window = self.expression(
             exp.Window,
             this=this,
             partition_by=partition,
@@ -4183,6 +4183,12 @@ class Parser(metaclass=_Parser):
             first=first,
         )
 
+        # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...)
+        if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False):
+            return self._parse_window(window, alias=alias)
+
+        return window
+
     def _parse_window_spec(self) -> t.Dict[str, t.Optional[str | exp.Expression]]:
         self._match(TokenType.BETWEEN)
 


Test patch:
diff --git a/tests/dialects/test_oracle.py b/tests/dialects/test_oracle.py
index f30b38fa..0c3b09f3 100644
--- a/tests/dialects/test_oracle.py
+++ b/tests/dialects/test_oracle.py
@@ -23,6 +23,11 @@ class TestOracle(Validator):
         self.validate_identity(
             "SELECT MIN(column_name) KEEP (DENSE_RANK FIRST ORDER BY column_name DESC) FROM table_name"
         )
+        self.validate_identity(
+            "SELECT last_name, department_id, salary, MIN(salary) KEEP (DENSE_RANK FIRST ORDER BY commission_pct) "
+            'OVER (PARTITION BY department_id) AS "Worst", MAX(salary) KEEP (DENSE_RANK LAST ORDER BY commission_pct) '
+            'OVER (PARTITION BY department_id) AS "Best" FROM employees ORDER BY department_id, salary, last_name'
+        )
 
         self.validate_all(
             "NVL(NULL, 1)",