Skip to content

Commit 8c929c3

Browse files
authored
Add type property to function nodes (#12057)
* Add `FunctionType` enum * Add `type` property to `Function` resource * Add `type` property to `ParsedFunctionPatch` and `UnparsedFunctionUpdate` * Begin populating a function's `type` during patch parsing * Regnerate v12 manifest to include function `type` property * Add changie doc * Begin testing that function node `type` property is setable and accessible * Move comment about triggering the PathEncoder back to its proper place
1 parent e949d1a commit 8c929c3

File tree

9 files changed

+103
-5
lines changed

9 files changed

+103
-5
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: Features
2+
body: Add `type` property to `function` nodes
3+
time: 2025-09-26T14:03:15.728495-05:00
4+
custom:
5+
Author: QMalcolm
6+
Issue: 12042 12037

core/dbt/artifacts/resources/types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,9 @@ class BatchSize(StrEnum):
7979

8080
def plural(self) -> str:
8181
return str(self) + "s"
82+
83+
84+
class FunctionType(StrEnum):
85+
Scalar = "scalar"
86+
Aggregate = "aggregate"
87+
Table = "table"

core/dbt/artifacts/resources/v1/function.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import dataclass, field
22
from typing import List, Literal, Optional
33

4-
from dbt.artifacts.resources.types import NodeType
4+
from dbt.artifacts.resources.types import FunctionType, NodeType
55
from dbt.artifacts.resources.v1.components import CompiledResource
66
from dbt.artifacts.resources.v1.config import NodeConfig
77
from dbt_common.dataclass_schema import dbtClassMixin
@@ -46,3 +46,4 @@ class Function(CompiledResource, FunctionMandatory):
4646
resource_type: Literal[NodeType.Function]
4747
config: FunctionConfig
4848
arguments: List[FunctionArgument] = field(default_factory=list)
49+
type: FunctionType = FunctionType.Scalar

core/dbt/contracts/graph/nodes.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
from dbt.artifacts.resources import SqlOperation as SqlOperationResource
6464
from dbt.artifacts.resources import TimeSpine
6565
from dbt.artifacts.resources import UnitTestDefinition as UnitTestDefinitionResource
66+
from dbt.artifacts.resources.types import FunctionType
6667
from dbt.artifacts.schemas.batch_results import BatchResults
6768
from dbt.clients.jinja_static import statically_extract_has_name_this
6869
from dbt.contracts.graph.model_config import UnitTestNodeConfig
@@ -1722,18 +1723,19 @@ class ParsedNodePatch(ParsedPatch):
17221723
freshness: Optional[ModelFreshness] = None
17231724

17241725

1725-
# TODO: Maybe this shouldn't be a subclass of ParsedNodePatch, but ParsedPatch instead
1726-
# Currently, `functions` have the fields like `columns`, `access`, `version`, and etc,
1727-
# but they don't actually do anything. If we remove those properties from FunctionNode,
1728-
# we can remove this class and use ParsedPatch instead.
17291726
@dataclass
17301727
class ParsedFunctionPatchRequired:
17311728
return_type: FunctionReturnType
17321729

17331730

1731+
# TODO: Maybe this shouldn't be a subclass of ParsedNodePatch, but ParsedPatch instead
1732+
# Currently, `functions` have the fields like `columns`, `access`, `version`, and etc,
1733+
# but they don't actually do anything. If we remove those properties from FunctionNode,
1734+
# we can remove this class and use ParsedPatch instead.
17341735
@dataclass
17351736
class ParsedFunctionPatch(ParsedNodePatch, ParsedFunctionPatchRequired):
17361737
arguments: List[FunctionArgument] = field(default_factory=list)
1738+
type: FunctionType = FunctionType.Scalar
17371739

17381740

17391741
@dataclass

core/dbt/contracts/graph/unparsed.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
list_str,
3232
metas,
3333
)
34+
from dbt.artifacts.resources.types import FunctionType
3435
from dbt.exceptions import ParsingError
3536
from dbt.node_types import NodeType
3637
from dbt_common.contracts.config.base import CompareBehavior, MergeBehavior
@@ -672,6 +673,7 @@ class UnparsedFunctionUpdate(
672673
):
673674
access: Optional[str] = None
674675
arguments: List[FunctionArgument] = field(default_factory=list)
676+
type: FunctionType = FunctionType.Scalar
675677

676678

677679
#

core/dbt/parser/schemas.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,7 @@ def patch_node_properties(self, node, patch: "ParsedNodePatch") -> None:
12921292

12931293
node.arguments = patch.arguments
12941294
node.return_type = patch.return_type
1295+
node.type = patch.type
12951296

12961297
def _get_node_patch(self, block: TargetBlock[NodeTarget], refs: ParserRef) -> ParsedNodePatch:
12971298
target = block.target
@@ -1315,6 +1316,7 @@ def _get_node_patch(self, block: TargetBlock[NodeTarget], refs: ParserRef) -> Pa
13151316
time_spine=None,
13161317
arguments=target.arguments,
13171318
return_type=target.return_type,
1319+
type=target.type,
13181320
)
13191321

13201322

schemas/dbt/manifest/v12.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9007,6 +9007,14 @@
90079007
"type"
90089008
]
90099009
}
9010+
},
9011+
"type": {
9012+
"enum": [
9013+
"scalar",
9014+
"aggregate",
9015+
"table"
9016+
],
9017+
"default": "scalar"
90109018
}
90119019
},
90129020
"additionalProperties": false,
@@ -20597,6 +20605,14 @@
2059720605
"type"
2059820606
]
2059920607
}
20608+
},
20609+
"type": {
20610+
"enum": [
20611+
"scalar",
20612+
"aggregate",
20613+
"table"
20614+
],
20615+
"default": "scalar"
2060020616
}
2060120617
},
2060220618
"additionalProperties": false,
@@ -27107,6 +27123,14 @@
2710727123
"type"
2710827124
]
2710927125
}
27126+
},
27127+
"type": {
27128+
"enum": [
27129+
"scalar",
27130+
"aggregate",
27131+
"table"
27132+
],
27133+
"default": "scalar"
2711027134
}
2711127135
},
2711227136
"additionalProperties": false,
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from typing import Dict
2+
3+
import pytest
4+
5+
from dbt.artifacts.resources import FunctionReturnType
6+
from dbt.artifacts.resources.types import FunctionType
7+
from dbt.contracts.graph.nodes import FunctionNode
8+
from dbt.tests.util import run_dbt
9+
10+
double_total_sql = """
11+
SELECT SUM(values) * 2
12+
"""
13+
14+
double_total_yml = """
15+
functions:
16+
- name: double_total
17+
type: aggregate
18+
description: Sums the sequence of numbers and then doubles the result
19+
arguments:
20+
- name: values
21+
type: float
22+
description: A sequence of numbers
23+
return_type:
24+
type: float
25+
"""
26+
27+
28+
class BasicUDAFSetup:
29+
@pytest.fixture(scope="class")
30+
def functions(self) -> Dict[str, str]:
31+
return {
32+
"double_total.sql": double_total_sql,
33+
"double_total.yml": double_total_yml,
34+
}
35+
36+
37+
class TestBasicSQLUDAF(BasicUDAFSetup):
38+
def test_basic_sql_udaf_parsing(self, project):
39+
manifest = run_dbt(["parse"])
40+
assert len(manifest.functions) == 1
41+
assert "function.test.double_total" in manifest.functions
42+
function_node = manifest.functions["function.test.double_total"]
43+
assert isinstance(function_node, FunctionNode)
44+
assert function_node.type == FunctionType.Aggregate
45+
assert (
46+
function_node.description == "Sums the sequence of numbers and then doubles the result"
47+
)
48+
assert len(function_node.arguments) == 1
49+
argument = function_node.arguments[0]
50+
assert argument.name == "values"
51+
assert argument.type == "float"
52+
assert argument.description == "A sequence of numbers"
53+
assert function_node.return_type == FunctionReturnType(type="float")

tests/functional/functions/test_udfs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
from dbt.artifacts.resources import FunctionReturnType
7+
from dbt.artifacts.resources.types import FunctionType
78
from dbt.contracts.graph.nodes import FunctionNode
89
from dbt.tests.util import run_dbt
910

@@ -40,6 +41,7 @@ def test_basic_sql_udf_parsing(self, project):
4041
assert "function.test.double_it" in manifest.functions
4142
function_node = manifest.functions["function.test.double_it"]
4243
assert isinstance(function_node, FunctionNode)
44+
assert function_node.type == FunctionType.Scalar
4345
assert function_node.description == "Doubles whatever number is passed in"
4446
assert len(function_node.arguments) == 1
4547
argument = function_node.arguments[0]

0 commit comments

Comments
 (0)