Repository: TobikoData/sqlmesh (python)

Issue:
Using Macros in Navigation Functions with IGNORE NULLS
Hello there,

I can use a SQLMesh Macro with no issue in a navigation function like so:
```sql
@DEF(test, x -> x);

SELECT FIRST_VALUE(@test(x)) OVER (ORDER BY y) AS column_test
   , x
   , y
FROM test;
```

However, adding `IGNORE NULLS` after the Macro call:
```sql
@DEF(test, x -> x);

SELECT FIRST_VALUE(@test(x) IGNORE NULLS) OVER (ORDER BY y) AS column_test
   , x
   , y
FROM test;
```

I get a `Error: Macro variable '' is undefined.` error.

Pull request description:
Fix: Using macros with IGNORE NULLS
Closes #2902

Patch:
diff --git a/sqlmesh/core/dialect.py b/sqlmesh/core/dialect.py
index 33bd240b..19407487 100644
--- a/sqlmesh/core/dialect.py
+++ b/sqlmesh/core/dialect.py
@@ -241,7 +241,7 @@ def _parse_macro(self: Parser, keyword_macro: str = "") -> t.Optional[exp.Expres
             return field
         return self.expression(MacroVar, this=field.this, comments=comments)
 
-    if isinstance(field, exp.Window):
+    if isinstance(field, (exp.Window, exp.IgnoreNulls, exp.RespectNulls)):
         field.set("this", _build_macro(field.this))
     else:
         field = _build_macro(field)


Test patch:
diff --git a/tests/core/test_macros.py b/tests/core/test_macros.py
index 72bde9ce..6b1fba20 100644
--- a/tests/core/test_macros.py
+++ b/tests/core/test_macros.py
@@ -3,7 +3,7 @@ import typing as t
 import pytest
 from sqlglot import MappingSchema, ParseError, exp, parse_one
 
-from sqlmesh.core import constants as c
+from sqlmesh.core import constants as c, dialect as d
 from sqlmesh.core.dialect import StagedFilePath
 from sqlmesh.core.macros import SQL, MacroEvalError, MacroEvaluator, macro
 from sqlmesh.utils.errors import SQLMeshError
@@ -638,3 +638,24 @@ def test_macro_metadata_flag():
 
     assert getattr(noop_metadata_only, c.SQLMESH_METADATA) is True
     assert macro.get_registry()["noop_metadata_only"].metadata_only is True
+
+
+def test_macro_first_value_ignore_respect_nulls(assert_exp_eq) -> None:
+    schema = MappingSchema({}, dialect="duckdb")
+    evaluator = MacroEvaluator(schema=schema, dialect="duckdb")
+
+    evaluator.evaluate(t.cast(d.MacroDef, d.parse_one("@DEF(test, x -> x)")))
+
+    expected_sql = "SELECT FIRST_VALUE(x IGNORE NULLS) OVER (ORDER BY y NULLS FIRST) AS column_test"
+    actual_expr = d.parse_one(
+        "SELECT FIRST_VALUE(@test(x) IGNORE NULLS) OVER (ORDER BY y) AS column_test"
+    )
+    assert_exp_eq(evaluator.transform(actual_expr), expected_sql, dialect="duckdb")
+
+    expected_sql = (
+        "SELECT FIRST_VALUE(x RESPECT NULLS) OVER (ORDER BY y NULLS FIRST) AS column_test"
+    )
+    actual_expr = d.parse_one(
+        "SELECT FIRST_VALUE(@test(x) RESPECT NULLS) OVER (ORDER BY y) AS column_test"
+    )
+    assert_exp_eq(evaluator.transform(actual_expr), expected_sql, dialect="duckdb")