Skip to content

Commit

Permalink
expression, planner: support builtin function benchmark
Browse files Browse the repository at this point in the history
implements builtin function `BENCHMARK()`, and introduces a counter
in expression_rewriter to disable constant folding. (pingcap#6774)
  • Loading branch information
wuudjac committed Feb 4, 2019
1 parent fe8e3f9 commit 7f37c92
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 31 deletions.
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
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)
}

// 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.
sameEvalType := args[1].GetType().EvalType()
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)
}

// 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++ {
_, 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 @@ -2490,6 +2490,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

0 comments on commit 7f37c92

Please sign in to comment.