Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expression, planner: support builtin function benchmark #9252

Merged
merged 6 commits into from
Feb 13, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion expression/builtin_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,56 @@ type benchmarkFunctionClass struct {
baseFunctionClass
}

// getFunction See https://dev.mysql.com/doc/refman/5.7/en/information-functions.html#function_benchmark
zz-jason marked this conversation as resolved.
Show resolved Hide resolved
func (c *benchmarkFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
return nil, errFunctionNotExists.GenWithStackByArgs("FUNCTION", "BENCHMARK")
if err := c.verifyArgs(args); err != nil {
return nil, errors.Trace(err)
zz-jason marked this conversation as resolved.
Show resolved Hide resolved
}

// Syntax: BENCHMARK(loop_count, expression)
// Now that eval result of input arg is useless, we can define by the same eval type of input arg here.
zz-jason marked this conversation as resolved.
Show resolved Hide resolved
sameEvalType := args[1].GetType().EvalType()
qw4990 marked this conversation as resolved.
Show resolved Hide resolved
bf := newBaseBuiltinFuncWithTp(ctx, args, types.ETInt, types.ETInt, sameEvalType)
sig := &builtinBenchmarkSig{bf}
return sig, nil
}

type builtinBenchmarkSig struct {
baseBuiltinFunc
}

func (b *builtinBenchmarkSig) Clone() builtinFunc {
newSig := &builtinBenchmarkSig{}
newSig.cloneFrom(&b.baseBuiltinFunc)
return newSig
}

func (b *builtinBenchmarkSig) evalInt(row chunk.Row) (int64, bool, error) {
// Get loop count.
loopCount, isNull, err := b.args[0].EvalInt(b.ctx, row)
if isNull || err != nil {
return 0, isNull, errors.Trace(err)
zz-jason marked this conversation as resolved.
Show resolved Hide resolved
}

// BENCHMARK() will return NULL if loop count < 0,
// behavior observed on MySQL 5.7.24.
if loopCount < 0 {
return 0, true, nil
}

// Eval loop count times.
var i int64
for ; i < loopCount; i++ {
zz-jason marked this conversation as resolved.
Show resolved Hide resolved
_, err := b.args[1].Eval(row)
// BENCHMARK() will pass-through the eval error,
// behavior observed on MySQL 5.7.24.
if err != nil {
return 0, false, errors.Trace(err)
}
}

// Return value of BENCHMARK() is always 0.
return 0, false, nil
}

type charsetFunctionClass struct {
Expand Down
33 changes: 29 additions & 4 deletions expression/builtin_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,35 @@ func (s *testEvaluatorSuite) TestVersion(c *C) {

func (s *testEvaluatorSuite) TestBenchMark(c *C) {
defer testleak.AfterTest(c)()
fc := funcs[ast.Benchmark]
f, err := fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(nil, nil)))
c.Assert(f, IsNil)
c.Assert(err, ErrorMatches, "*FUNCTION BENCHMARK does not exist")

cases := []struct {
LoopCount int
Expression interface{}
Expected int64
IsNil bool
}{
{-3, 1, 0, true},
{0, 1, 0, false},
{3, 1, 0, false},
{3, 1.234, 0, false},
{3, "abc", 0, false},
}

for _, t := range cases {
f, err := newFunctionForTest(s.ctx, ast.Benchmark, s.primitiveValsToConstants([]interface{}{
t.LoopCount,
t.Expression,
})...)
c.Assert(err, IsNil)

d, err := f.Eval(chunk.Row{})
c.Assert(err, IsNil)
if t.IsNil {
c.Assert(d.IsNull(), IsTrue)
} else {
c.Assert(d.GetInt64(), Equals, t.Expected)
}
}
}

func (s *testEvaluatorSuite) TestCharset(c *C) {
Expand Down
8 changes: 8 additions & 0 deletions expression/function_traits.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ var unFoldableFunctions = map[string]struct{}{
ast.SetVar: {},
ast.GetVar: {},
ast.GetParam: {},
ast.Benchmark: {},
}

// DisableFoldFunctions stores functions which prevent child scope functions from being constant folded.
// Typically, these functions shall also exist in unFoldableFunctions, to stop from being folded when they themselves
// are in child scope of an outer function, and the outer function is recursively folding its children.
var DisableFoldFunctions = map[string]struct{}{
ast.Benchmark: {},
}

// DeferredFunctions stores non-deterministic functions, which can be deferred only when the plan cache is enabled.
Expand Down
36 changes: 36 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2489,6 +2489,42 @@ func (s *testIntegrationSuite) TestInfoBuiltin(c *C) {
result.Check(testkit.Rows("1"))
result = tk.MustQuery("select row_count();")
result.Check(testkit.Rows("-1"))

// for benchmark
success := testkit.Rows("0")
tk.MustExec("drop table if exists t")
tk.MustExec("create table t (a int, b int)")
result = tk.MustQuery(`select benchmark(3, benchmark(2, length("abc")))`)
result.Check(success)
err := tk.ExecToErr(`select benchmark(3, length("a", "b"))`)
c.Assert(err, NotNil)
// Quoted from https://dev.mysql.com/doc/refman/5.7/en/information-functions.html#function_benchmark
// Although the expression can be a subquery, it must return a single column and at most a single row.
// For example, BENCHMARK(10, (SELECT * FROM t)) will fail if the table t has more than one column or
// more than one row.
oneColumnQuery := "select benchmark(10, (select a from t))"
twoColumnQuery := "select benchmark(10, (select * from t))"
// rows * columns:
// 0 * 1, success;
result = tk.MustQuery(oneColumnQuery)
result.Check(success)
// 0 * 2, error;
err = tk.ExecToErr(twoColumnQuery)
c.Assert(err, NotNil)
// 1 * 1, success;
tk.MustExec("insert t values (1, 2)")
result = tk.MustQuery(oneColumnQuery)
result.Check(success)
// 1 * 2, error;
err = tk.ExecToErr(twoColumnQuery)
c.Assert(err, NotNil)
// 2 * 1, error;
tk.MustExec("insert t values (3, 4)")
err = tk.ExecToErr(oneColumnQuery)
c.Assert(err, NotNil)
// 2 * 2, error.
err = tk.ExecToErr(twoColumnQuery)
c.Assert(err, NotNil)
}

func (s *testIntegrationSuite) TestControlBuiltin(c *C) {
Expand Down
Loading