Skip to content

Commit 5babece

Browse files
authored
Feat: add optional @limit macro (#2275)
* wip: add plumbing for limit structural macro * feat: update to consume required upstream api change * chore: add tested doc examples of usage to optional_expression function
1 parent f795506 commit 5babece

File tree

4 files changed

+61
-127
lines changed

4 files changed

+61
-127
lines changed

docs/concepts/macros/sqlmesh_macros.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ SQLMesh's macro system has six operators that correspond to different clauses in
526526
- `@GROUP_BY`: grouping `GROUP BY` clause
527527
- `@HAVING`: group by filtering `HAVING` clause
528528
- `@ORDER_BY`: ordering `ORDER BY` clause
529+
- `@LIMIT`: limiting `LIMIT` clause
529530

530531
Each of these operators is used to dynamically add the code for its corresponding clause to a model's SQL query.
531532

@@ -746,6 +747,22 @@ FROM all_cities
746747
ORDER BY city_pop
747748
```
748749

750+
#### @LIMIT operator
751+
752+
```sql linenums="1"
753+
SELECT *
754+
FROM all_cities
755+
@LIMIT(True) 10
756+
```
757+
758+
renders to
759+
760+
```sql linenums="1"
761+
SELECT *
762+
FROM all_cities
763+
LIMIT 10
764+
```
765+
749766
## User-defined macro functions
750767

751768
User-defined macro functions allow the same macro code to be used in multiple models.

sqlmesh/core/dialect.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def _parse_macro(self: Parser, keyword_macro: str = "") -> t.Optional[exp.Expres
225225
return self.expression(MacroVar, this=field.this)
226226

227227

228-
KEYWORD_MACROS = {"WITH", "JOIN", "WHERE", "GROUP_BY", "HAVING", "ORDER_BY"}
228+
KEYWORD_MACROS = {"WITH", "JOIN", "WHERE", "GROUP_BY", "HAVING", "ORDER_BY", "LIMIT"}
229229

230230

231231
def _parse_matching_macro(self: Parser, name: str) -> t.Optional[exp.Expression]:
@@ -251,6 +251,8 @@ def _parse_body_macro(self: Parser) -> t.Tuple[str, t.Optional[exp.Expression]]:
251251
return ("having", self._parse_having())
252252
if name == "ORDER_BY":
253253
return ("order", self._parse_order())
254+
if name == "LIMIT":
255+
return ("limit", self._parse_limit())
254256
return ("", None)
255257

256258

@@ -323,6 +325,20 @@ def _parse_order(
323325
return macro
324326

325327

328+
def _parse_limit(
329+
self: Parser,
330+
this: t.Optional[exp.Expression] = None,
331+
top: bool = False,
332+
skip_limit_token: bool = False,
333+
) -> t.Optional[exp.Expression]:
334+
macro = _parse_matching_macro(self, "TOP" if top else "LIMIT")
335+
if not macro:
336+
return self.__parse_limit(this, top=top, skip_limit_token=skip_limit_token) # type: ignore
337+
338+
macro.this.append("expressions", self.__parse_limit(this, top=top, skip_limit_token=True)) # type: ignore
339+
return macro
340+
341+
326342
def _parse_props(self: Parser) -> t.Optional[exp.Expression]:
327343
key = self._parse_id_var(any_token=True)
328344
if not key:
@@ -762,6 +778,7 @@ def extend_sqlglot() -> None:
762778
_override(Parser, _parse_group)
763779
_override(Parser, _parse_with)
764780
_override(Parser, _parse_having)
781+
_override(Parser, _parse_limit)
765782
_override(Parser, _parse_lambda)
766783
_override(Parser, _parse_types)
767784
_override(Parser, _parse_if)

sqlmesh/core/macros.py

Lines changed: 16 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -539,158 +539,48 @@ def filter_(evaluator: MacroEvaluator, *args: t.Any) -> t.List[t.Any]:
539539
return list(filter(lambda arg: evaluator.eval_expression(func(arg)), items))
540540

541541

542-
@macro("WITH")
543-
def with_(
542+
def _optional_expression(
544543
evaluator: MacroEvaluator,
545544
condition: exp.Condition,
546-
expression: exp.With,
547-
) -> t.Optional[exp.With]:
548-
"""Inserts WITH expression when the condition is True
545+
expression: exp.Expression,
546+
) -> t.Optional[exp.Expression]:
547+
"""Inserts expression when the condition is True
549548
550-
Example:
549+
The following examples express the usage of this function in the context of the macros which wrap it.
550+
551+
Examples:
551552
>>> from sqlglot import parse_one
552553
>>> from sqlmesh.core.macros import MacroEvaluator
553554
>>> sql = "@WITH(True) all_cities as (select * from city) select all_cities"
554555
>>> MacroEvaluator().transform(parse_one(sql)).sql()
555556
'WITH all_cities AS (SELECT * FROM city) SELECT all_cities'
556-
557-
Args:
558-
evaluator: MacroEvaluator that invoked the macro
559-
condition: Condition expression
560-
expression: With expression
561-
Returns:
562-
With expression if the conditional is True; otherwise None
563-
"""
564-
return expression if evaluator.eval_expression(condition) else None
565-
566-
567-
@macro()
568-
def join(
569-
evaluator: MacroEvaluator,
570-
condition: exp.Condition,
571-
expression: exp.Join,
572-
) -> t.Optional[exp.Join]:
573-
"""Inserts JOIN expression when the condition is True
574-
575-
Example:
576-
>>> from sqlglot import parse_one
577-
>>> from sqlmesh.core.macros import MacroEvaluator
578-
>>> sql = "select * from city @JOIN(True) country on city.country = country.name"
579-
>>> MacroEvaluator().transform(parse_one(sql)).sql()
580-
'SELECT * FROM city JOIN country ON city.country = country.name'
581-
582557
>>> sql = "select * from city left outer @JOIN(True) country on city.country = country.name"
583558
>>> MacroEvaluator().transform(parse_one(sql)).sql()
584559
'SELECT * FROM city LEFT OUTER JOIN country ON city.country = country.name'
585-
586-
Args:
587-
evaluator: MacroEvaluator that invoked the macro
588-
condition: Condition expression
589-
expression: Join expression
590-
Returns:
591-
Join expression if the conditional is True; otherwise None
592-
"""
593-
return expression if evaluator.eval_expression(condition) else None
594-
595-
596-
@macro()
597-
def where(
598-
evaluator: MacroEvaluator,
599-
condition: exp.Condition,
600-
expression: exp.Where,
601-
) -> t.Optional[exp.Where]:
602-
"""Inserts WHERE expression when the condition is True
603-
604-
Example:
605-
>>> from sqlglot import parse_one
606-
>>> from sqlmesh.core.macros import MacroEvaluator
607-
>>> sql = "select * from city @WHERE(True) population > 100 and country = 'Mexico'"
608-
>>> MacroEvaluator().transform(parse_one(sql)).sql()
609-
"SELECT * FROM city WHERE population > 100 AND country = 'Mexico'"
610-
611-
Args:
612-
evaluator: MacroEvaluator that invoked the macro
613-
condition: Condition expression
614-
expression: Where expression
615-
Returns:
616-
Where expression if condition is True; otherwise None
617-
"""
618-
return expression if evaluator.eval_expression(condition) else None
619-
620-
621-
@macro()
622-
def group_by(
623-
evaluator: MacroEvaluator,
624-
condition: exp.Condition,
625-
expression: exp.Group,
626-
) -> t.Optional[exp.Group]:
627-
"""Inserts GROUP BY expression when the condition is True
628-
629-
Example:
630-
>>> from sqlglot import parse_one
631-
>>> from sqlmesh.core.macros import MacroEvaluator, group_by
632560
>>> sql = "select * from city @GROUP_BY(True) country, population"
633561
>>> MacroEvaluator().transform(parse_one(sql)).sql()
634562
'SELECT * FROM city GROUP BY country, population'
635-
636-
Args:
637-
evaluator: MacroEvaluator that invoked the macro
638-
condition: Condition expression
639-
expression: Group expression
640-
Returns:
641-
Group expression if the condition is True; otherwise None
642-
"""
643-
return expression if evaluator.eval_expression(condition) else None
644-
645-
646-
@macro()
647-
def having(
648-
evaluator: MacroEvaluator,
649-
condition: exp.Condition,
650-
expression: exp.Having,
651-
) -> t.Optional[exp.Having]:
652-
"""Inserts HAVING expression when the condition is True
653-
654-
Example:
655-
>>> from sqlglot import parse_one
656-
>>> from sqlmesh.core.macros import MacroEvaluator
657563
>>> sql = "select * from city group by country @HAVING(True) population > 100 and country = 'Mexico'"
658564
>>> MacroEvaluator().transform(parse_one(sql)).sql()
659565
"SELECT * FROM city GROUP BY country HAVING population > 100 AND country = 'Mexico'"
660566
661567
Args:
662568
evaluator: MacroEvaluator that invoked the macro
663569
condition: Condition expression
664-
expression: Having expression
570+
expression: SQL expression
665571
Returns:
666-
Having expression if the condition is True; otherwise None
572+
Expression if the conditional is True; otherwise None
667573
"""
668574
return expression if evaluator.eval_expression(condition) else None
669575

670576

671-
@macro()
672-
def order_by(
673-
evaluator: MacroEvaluator,
674-
condition: exp.Condition,
675-
expression: exp.Order,
676-
) -> t.Optional[exp.Order]:
677-
"""Inserts ORDER BY expression when the condition is True
678-
679-
Example:
680-
>>> from sqlglot import parse_one
681-
>>> from sqlmesh.core.macros import MacroEvaluator
682-
>>> sql = "select * from city @ORDER_BY(True) population, name DESC"
683-
>>> MacroEvaluator().transform(parse_one(sql)).sql()
684-
'SELECT * FROM city ORDER BY population, name DESC'
685-
686-
Args:
687-
evaluator: MacroEvaluator that invoked the macro
688-
condition: Condition expression
689-
expression: Order expression
690-
Returns:
691-
Order expression if the condition is True; otherwise None
692-
"""
693-
return expression if evaluator.eval_expression(condition) else None
577+
with_ = macro("WITH")(_optional_expression)
578+
join = macro("JOIN")(_optional_expression)
579+
where = macro("WHERE")(_optional_expression)
580+
group_by = macro("GROUP_BY")(_optional_expression)
581+
having = macro("HAVING")(_optional_expression)
582+
order_by = macro("ORDER_BY")(_optional_expression)
583+
limit = macro("LIMIT")(_optional_expression)
694584

695585

696586
@macro("eval")

tests/core/test_macros.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,16 @@ def test_ast_correctness(macro_evaluator):
272272
"SELECT * FROM city",
273273
{"do_order": False},
274274
),
275+
(
276+
"""select * from city @LIMIT(@do_limit) 10""",
277+
"SELECT * FROM city LIMIT 10",
278+
{"do_limit": True},
279+
),
280+
(
281+
"""select * from city @LIMIT(@do_limit) 10""",
282+
"SELECT * FROM city",
283+
{"do_limit": False},
284+
),
275285
(
276286
"""select @if(TRUE, 1, 0)""",
277287
"SELECT 1",

0 commit comments

Comments
 (0)