Skip to content

Commit

Permalink
opt: build UDF expressions
Browse files Browse the repository at this point in the history
This commit adds basic support for building UDFs in optbuilder. Only
scalar, nullary (arity of zero) functions with a single statement in the
body are supported. Support for more types of UDFs will follow in future
commits. Note that this commit does not add support for execution of
UDFs, only building them within an optimizer expression.

Release note: None
  • Loading branch information
mgartner committed Jul 25, 2022
1 parent b138769 commit 71c8e32
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 1 deletion.
3 changes: 3 additions & 0 deletions pkg/sql/opt/memo/expr_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,9 @@ func FormatPrivate(f *ExprFmtCtx, private interface{}, physProps *physical.Requi
case *FunctionPrivate:
fmt.Fprintf(f.Buffer, " %s", t.Name)

case *UserDefinedFunctionPrivate:
fmt.Fprintf(f.Buffer, " %s", t.Name)

case *WindowsItemPrivate:
fmt.Fprintf(f.Buffer, " frame=%q", &t.Frame)

Expand Down
4 changes: 4 additions & 0 deletions pkg/sql/opt/norm/decorrelate_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func (c *CustomFuncs) deriveHasHoistableSubquery(scalar opt.ScalarExpr) bool {
// WHERE clause, it will be transformed to an Exists operator, so this case
// only occurs when the Any is nested, in a projection, etc.
return !t.Input.Relational().OuterCols.Empty()

case *memo.UserDefinedFunctionExpr:
// Do not attempt to hoist UDFs.
return false
}

// If HasHoistableSubquery is true for any child, then it's true for this
Expand Down
21 changes: 21 additions & 0 deletions pkg/sql/opt/norm/testdata/rules/udf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
exec-ddl
CREATE FUNCTION one() RETURNS INT LANGUAGE SQL AS 'SELECT 1';
----

# Do not attempt to hoist UDFs.
norm
SELECT one()
----
values
├── columns: one:2
├── cardinality: [1 - 1]
├── key: ()
├── fd: ()-->(2)
└── tuple
└── user-defined-function: one
└── values
├── columns: "?column?":1!null
├── cardinality: [1 - 1]
├── key: ()
├── fd: ()-->(1)
└── (1,)
15 changes: 15 additions & 0 deletions pkg/sql/opt/ops/scalar.opt
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,21 @@ define NthValue {
Nth ScalarExpr
}

# UserDefinedFunction invokes a user-defined function. The
# UserDefinedFunctionPrivate field contains the name of the function and a
# pointer to its type.
[Scalar]
define UserDefinedFunction {
Body RelExpr
_ UserDefinedFunctionPrivate
}

[Private]
define UserDefinedFunctionPrivate {
Name string
Typ Type
}

# KVOptions is a set of KVOptionItems that specify arbitrary keys and values
# that are used as modifiers for various statements (see tree.KVOptions). The
# key is a constant string but the value can be a scalar expression.
Expand Down
34 changes: 34 additions & 0 deletions pkg/sql/opt/optbuilder/scalar.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/opt/cat"
"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
"github.com/cockroachdb/cockroach/pkg/sql/opt/norm"
"github.com/cockroachdb/cockroach/pkg/sql/parser"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
"github.com/cockroachdb/cockroach/pkg/sql/privilege"
Expand Down Expand Up @@ -529,6 +530,10 @@ func (b *Builder) buildFunction(
panic(err)
}

if f.ResolvedOverload().Body != "" {
return b.buildUDF(f, def, inScope, outScope, outCol)
}

if isAggregate(def) {
panic(errors.AssertionFailedf("aggregate function should have been replaced"))
}
Expand Down Expand Up @@ -583,6 +588,35 @@ func (b *Builder) buildFunction(
return b.finishBuildScalar(f, out, inScope, outScope, outCol)
}

// buildUDF builds a set of memo groups that represents a user-defined function
// invocation.
// TODO(mgartner): Support multi-statement UDFs.
// TODO(mgartner): Support UDFs with arguments.
func (b *Builder) buildUDF(
f *tree.FuncExpr, def *tree.FunctionDefinition, inScope, outScope *scope, outCol *scopeColumn,
) (out opt.ScalarExpr) {
stmt, err := parser.ParseOne(f.ResolvedOverload().Body)
if err != nil {
panic(err)
}

// A statement inside a UDF body cannot refer to anything from the outer
// expression calling the function, so we use an empty scope.
// TODO(mgartner): We may need to set bodyScope.atRoot=true to prevent CTEs
// that mutate and are not at the top-level.
bodyScope := b.allocScope()
bodyScope = b.buildStmt(stmt.AST, nil /* desiredTypes */, bodyScope)

out = b.factory.ConstructUserDefinedFunction(
bodyScope.expr,
&memo.UserDefinedFunctionPrivate{
Name: def.Name,
Typ: f.ResolvedType(),
},
)
return b.finishBuildScalar(f, out, inScope, outScope, outCol)
}

// buildRangeCond builds a RANGE clause as a simpler expression. Examples:
// x BETWEEN a AND b -> x >= a AND x <= b
// x NOT BETWEEN a AND b -> NOT (x >= a AND x <= b)
Expand Down
66 changes: 65 additions & 1 deletion pkg/sql/opt/optbuilder/testdata/udf
Original file line number Diff line number Diff line change
@@ -1,4 +1,68 @@
exec-ddl
CREATE TABLE abc (
a INT PRIMARY KEY,
b INT,
c INT
)
----

build
SELECT foo()
----
error (42883): unknown function: foo()
error (42883): unknown function: foo

exec-ddl
CREATE FUNCTION one() RETURNS INT LANGUAGE SQL AS 'SELECT 1';
----

build
SELECT one()
----
project
├── columns: one:2
├── values
│ └── ()
└── projections
└── user-defined-function: one [as=one:2]
└── project
├── columns: "?column?":1!null
├── values
│ └── ()
└── projections
└── 1 [as="?column?":1]

build
SELECT *, one() FROM abc
----
project
├── columns: a:1!null b:2 c:3 one:7
├── scan abc
│ └── columns: a:1!null b:2 c:3 crdb_internal_mvcc_timestamp:4 tableoid:5
└── projections
└── user-defined-function: one [as=one:7]
└── project
├── columns: "?column?":6!null
├── values
│ └── ()
└── projections
└── 1 [as="?column?":6]

build
SELECT * FROM abc WHERE one() = c
----
project
├── columns: a:1!null b:2 c:3
└── select
├── columns: a:1!null b:2 c:3 crdb_internal_mvcc_timestamp:4 tableoid:5
├── scan abc
│ └── columns: a:1!null b:2 c:3 crdb_internal_mvcc_timestamp:4 tableoid:5
└── filters
└── eq
├── user-defined-function: one
│ └── project
│ ├── columns: "?column?":6!null
│ ├── values
│ │ └── ()
│ └── projections
│ └── 1 [as="?column?":6]
└── c:3

0 comments on commit 71c8e32

Please sign in to comment.