Skip to content

Commit

Permalink
sqlsmith: add support for creating udfs
Browse files Browse the repository at this point in the history
This PR gives sqlsmith the capability to issue `CREATE FUNCTION`
commands. It randomly chooses from available and valid function options.
It also contains up to 10 `SELECT` statements in the UDF body.

This PR also leaves several TODOs, including support for params, return
types other than records, and calling UDFs in queries after they are
created.

Epic: CRDB-20370
Informs: #90782

Release note: None
  • Loading branch information
rharding6373 committed Apr 11, 2023
1 parent b36c119 commit b9daecf
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 2 deletions.
4 changes: 2 additions & 2 deletions pkg/ccl/changefeedccl/changefeed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ func TestChangefeedRandomExpressions(t *testing.T) {
sqlsmith.DisableAggregateFuncs(),
sqlsmith.DisableWindowFuncs(),
sqlsmith.DisableJoins(),
sqlsmith.DisableLimits(),
sqlsmith.DisableUDFs(),
sqlsmith.DisableIndexHints(),
sqlsmith.SetScalarComplexity(0.5),
sqlsmith.SetComplexity(0.5),
Expand Down Expand Up @@ -6538,7 +6538,7 @@ func TestChangefeedBackfillCheckpoint(t *testing.T) {
progress := loadProgress()
require.NotNil(t, progress.GetChangefeed())
h := progress.GetHighWater()
noHighWater := (h == nil || h.IsEmpty())
noHighWater := h == nil || h.IsEmpty()
require.True(t, noHighWater)

jobCheckpoint := progress.GetChangefeed().Checkpoint
Expand Down
111 changes: 111 additions & 0 deletions pkg/internal/sqlsmith/relational.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
package sqlsmith

import (
"strings"

"github.com/cockroachdb/cockroach/pkg/sql/randgen"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree/treecmp"
Expand Down Expand Up @@ -108,6 +110,7 @@ var (
}
nonMutatingStatements = []statementWeight{
{10, makeSelect},
{1, makeCreateFunc},
}
allStatements = append(mutatingStatements, nonMutatingStatements...)

Expand Down Expand Up @@ -870,6 +873,114 @@ func (s *Smither) makeSelectList(
return result, selectRefs, true
}

func makeCreateFunc(s *Smither) (tree.Statement, bool) {
if s.disableUDFs {
return nil, false
}
return s.makeCreateFunc()
}

func (s *Smither) makeCreateFunc() (cf *tree.CreateFunction, ok bool) {
fname := s.name("func")
name := tree.MakeFunctionNameFromPrefix(tree.ObjectNamePrefix{}, fname)
// Return a record, which means the UDF can return any number or type in its
// final SQL statement.
// TODO(harding): Return scalars, UDTs, and tables in addition to records.
// Return multiple rows with the SETOF option about 33% of the time.
setof := false
if s.d6() < 3 {
setof = true
}
rtype := tree.FuncReturnType{
Type: types.AnyTuple,
IsSet: setof,
}
// TODO(harding): Test with multiple input params.
// TODO(harding): Allow params to be referenced in the statement body.
var params tree.FuncParams

// There are up to 5 function options that may be applied to UDFs.
var opts tree.FunctionOptions
opts = make(tree.FunctionOptions, 0, 5)

// FunctionNullInputBehavior
// 50%: Do not specify behavior (default is FunctionCalledOnNullInput).
// 15%: FunctionCalledOnNullInput
// 15%: FunctionReturnsNullOnNullInput
// 15%: FunctionStrict
switch s.d6() {
case 1:
opts = append(opts, tree.FunctionCalledOnNullInput)
case 2:
opts = append(opts, tree.FunctionReturnsNullOnNullInput)
case 3:
opts = append(opts, tree.FunctionStrict)
}

// FunctionVolatility
// 50%: Do not specify behavior (default is volatile).
// 15%: FunctionVolatile
// 15%: FunctionImmutable
// 15%: FunctionStable
immutable := false
switch s.d6() {
case 1:
opts = append(opts, tree.FunctionVolatile)
case 2:
opts = append(opts, tree.FunctionImmutable)
immutable = true
case 3:
opts = append(opts, tree.FunctionStable)
}

// FunctionLeakproof
// Leakproof can only be used with immutable volatility. If the function is
// immutable, also specify leakproof 50% of the time. Otherwise, specify
// not leakproof 50% of the time (default is not leakproof).
leakproof := false
if immutable {
leakproof = s.coin()
}
if leakproof || s.coin() {
opts = append(opts, tree.FunctionLeakproof(leakproof))
}

// FunctionLanguage
// Currently only SQL is supported.
opts = append(opts, tree.FunctionLangSQL)

// FunctionBodyStr
// Generate SQL statements for the function body. More than one may be
// generated, but only the result of the final statement will matter for
// the function return type. Use the FunctionBodyStr option so the statements
// are formatted correctly.
stmtCnt := s.rnd.Intn(10)
stmts := make([]string, 0, stmtCnt)
// TODO(harding): Make the desired types of the final statement match the
// function return type.
for i := 0; i < stmtCnt; i++ {
// UDFs currently only support SELECT statements.
stmt, _, ok := s.makeSelect(nil, nil)
if !ok {
continue
}
stmts = append(stmts, stmt.String())
}
if len(stmts) == 0 {
return nil, false
}
opts = append(opts, tree.FunctionBodyStr(strings.Join(stmts, "\n")))

stmt := &tree.CreateFunction{
FuncName: name,
ReturnType: rtype,
Params: params,
Options: opts,
}
// TODO(harding): Register existing functions so we can refer to them in queries.
return stmt, true
}

func makeDelete(s *Smither) (tree.Statement, bool) {
stmt, _, ok := s.makeDelete(nil)
return stmt, ok
Expand Down
6 changes: 6 additions & 0 deletions pkg/internal/sqlsmith/sqlsmith.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ type Smither struct {
disableInsertSelect bool
disableDivision bool
disableDecimals bool
disableUDFs bool

bulkSrv *httptest.Server
bulkFiles map[string][]byte
Expand Down Expand Up @@ -505,6 +506,11 @@ var DisableDecimals = simpleOption("disable decimals", func(s *Smither) {
s.disableDecimals = true
})

// DisableUDFs causes the Smither to disable user-defined functions.
var DisableUDFs = simpleOption("disable udfs", func(s *Smither) {
s.disableUDFs = true
})

// CompareMode causes the Smither to generate statements that have
// deterministic output.
var CompareMode = multiOption(
Expand Down

0 comments on commit b9daecf

Please sign in to comment.