Skip to content

Commit 8c2f9e3

Browse files
authored
Feat: allow a single macro function call in model definitions (#3032)
1 parent 8663923 commit 8c2f9e3

File tree

3 files changed

+41
-9
lines changed

3 files changed

+41
-9
lines changed

sqlmesh/core/model/definition.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,8 +1673,10 @@ def load_sql_based_model(
16731673
if query_or_seed_insert is not None and (
16741674
isinstance(query_or_seed_insert, (exp.Query, d.JinjaQuery))
16751675
or (
1676+
# Macro functions are allowed in place of model queries only when there are no
1677+
# other statements in the model definition, otherwise they would be ambiguous
16761678
isinstance(query_or_seed_insert, d.MacroFunc)
1677-
and query_or_seed_insert.this.name.lower() == "union"
1679+
and (query_or_seed_insert.this.name.lower() == "union" or len(expressions) == 2)
16781680
)
16791681
):
16801682
return create_sql_model(
@@ -1745,9 +1747,6 @@ def create_sql_model(
17451747
used_variables: The set of variable names used by this model.
17461748
"""
17471749
if not isinstance(query, (exp.Query, d.JinjaQuery, d.MacroFunc)):
1748-
# Users are not expected to pass in a single MacroFunc instance for a model's query;
1749-
# this is an implementation detail which allows us to create python models that return
1750-
# SQL, either in the form of SQLGlot expressions or just plain strings.
17511750
raise_config_error(
17521751
"A query is required and must be a SELECT statement, a UNION statement, or a JINJA_QUERY block",
17531752
path,
@@ -2081,7 +2080,10 @@ def _split_sql_model_statements(
20812080
if (
20822081
isinstance(expr, (exp.Query, d.JinjaQuery))
20832082
or expr == INSERT_SEED_MACRO_CALL
2084-
or (isinstance(expr, d.MacroFunc) and expr.this.name.lower() == "union")
2083+
or (
2084+
isinstance(expr, d.MacroFunc)
2085+
and (expr.this.name.lower() == "union" or length == 1)
2086+
)
20852087
):
20862088
query_positions.append((expr, idx))
20872089
sql_statements.append(expr)

sqlmesh/core/renderer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,9 @@ def render(
431431
if not query:
432432
return None
433433
if not isinstance(query, exp.Query):
434-
raise_config_error(f"Query needs to be a SELECT or a UNION {query}.", self._path)
434+
raise_config_error(
435+
f"Model query needs to be a SELECT or a UNION, got {query}.", self._path
436+
)
435437
raise
436438

437439
if optimize:

tests/core/test_model.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,12 +376,40 @@ def test_no_query():
376376
);
377377
378378
@DEF(x, 1)
379-
"""
379+
"""
380380
)
381381

382382
with pytest.raises(ConfigError) as ex:
383-
load_sql_based_model(expressions, path=Path("test_location"))
384-
assert "have a SELECT" in str(ex.value)
383+
model = load_sql_based_model(expressions, path=Path("test_location"))
384+
model.validate_definition()
385+
386+
assert "Model query needs to be a SELECT or a UNION, got @DEF(x, 1)." in str(ex.value)
387+
388+
389+
def test_single_macro_as_query(assert_exp_eq):
390+
@macro()
391+
def select_query(evaluator, *projections):
392+
return exp.select(*[f'{p} AS "{p}"' for p in projections])
393+
394+
expressions = d.parse(
395+
"""
396+
MODEL (
397+
name test
398+
);
399+
400+
@SELECT_QUERY(1, 2, 3)
401+
"""
402+
)
403+
model = load_sql_based_model(expressions)
404+
assert_exp_eq(
405+
model.render_query(),
406+
"""
407+
SELECT
408+
1 AS "1",
409+
2 AS "2",
410+
3 AS "3"
411+
""",
412+
)
385413

386414

387415
def test_partition_key_is_missing_in_query():

0 commit comments

Comments
 (0)