From 73ec0999f30ed0dbd5b3331172e48c4109e753dc Mon Sep 17 00:00:00 2001 From: Feng Liyuan Date: Sat, 21 Sep 2019 18:41:59 +0800 Subject: [PATCH] expression: go generate vectorized addtime functions --- expression/builtin.go | 1 + expression/builtin_cast_vec.go | 35 ++ expression/builtin_cast_vec_test.go | 20 + expression/builtin_time_vec_generated.go | 593 ++++++++++++++++++ expression/builtin_time_vec_generated_test.go | 182 ++++++ expression/generator/helper/helper.go | 46 ++ expression/generator/time_vec.go | 410 ++++++++++++ types/overflow.go | 11 + types/overflow_test.go | 7 + 9 files changed, 1305 insertions(+) create mode 100644 expression/builtin_time_vec_generated.go create mode 100644 expression/builtin_time_vec_generated_test.go create mode 100644 expression/generator/helper/helper.go create mode 100644 expression/generator/time_vec.go diff --git a/expression/builtin.go b/expression/builtin.go index c4bda95176292..f4b5cf9ddac1a 100644 --- a/expression/builtin.go +++ b/expression/builtin.go @@ -16,6 +16,7 @@ // limitations under the License. //go:generate go run generator/control_vec.go +//go:generate go run generator/time_vec.go package expression diff --git a/expression/builtin_cast_vec.go b/expression/builtin_cast_vec.go index 065f3ccec51bd..b51c5be0a4612 100644 --- a/expression/builtin_cast_vec.go +++ b/expression/builtin_cast_vec.go @@ -143,3 +143,38 @@ func (b *builtinCastRealAsRealSig) vecEvalReal(input *chunk.Chunk, result *chunk func (b *builtinCastRealAsRealSig) vectorized() bool { return true } + +func (b *builtinCastTimeAsDurationSig) vecEvalDuration(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + arg0, err := b.bufAllocator.get(types.ETDatetime, n) + if err != nil { + return err + } + defer b.bufAllocator.put(arg0) + if err := b.args[0].VecEvalTime(b.ctx, input, arg0); err != nil { + return err + } + arg0s := arg0.Times() + result.ResizeGoDuration(n, false) + result.MergeNulls(arg0) + ds := result.GoDurations() + for i, t := range arg0s { + if result.IsNull(i) { + continue + } + d, err := t.ConvertToDuration() + if err != nil { + return err + } + d, err = d.RoundFrac(int8(b.tp.Decimal)) + if err != nil { + return err + } + ds[i] = d.Duration + } + return nil +} + +func (b *builtinCastTimeAsDurationSig) vectorized() bool { + return true +} diff --git a/expression/builtin_cast_vec_test.go b/expression/builtin_cast_vec_test.go index c143724c10c82..719070ead472e 100644 --- a/expression/builtin_cast_vec_test.go +++ b/expression/builtin_cast_vec_test.go @@ -27,9 +27,29 @@ var vecBuiltinCastCases = map[string][]vecExprBenchCase{ {retEvalType: types.ETReal, childrenTypes: []types.EvalType{types.ETInt}}, {retEvalType: types.ETDuration, childrenTypes: []types.EvalType{types.ETInt}, geners: []dataGenerator{new(randDurInt)}}, {retEvalType: types.ETReal, childrenTypes: []types.EvalType{types.ETReal}}, + {retEvalType: types.ETDuration, childrenTypes: []types.EvalType{types.ETDatetime}, + geners: []dataGenerator{&dateTimeGenerWithFsp{ + defaultGener: defaultGener{nullRation: 0.2, eType: types.ETDatetime}, + fsp: 1, + }}, + }, }, } +type dateTimeGenerWithFsp struct { + defaultGener + fsp int8 +} + +func (g *dateTimeGenerWithFsp) gen() interface{} { + result := g.defaultGener.gen() + if t, ok := result.(types.Time); ok { + t.Fsp = g.fsp + return t + } + return result +} + func (s *testEvaluatorSuite) TestVectorizedBuiltinCastEvalOneVec(c *C) { testVectorizedEvalOneVec(c, vecBuiltinCastCases) } diff --git a/expression/builtin_time_vec_generated.go b/expression/builtin_time_vec_generated.go new file mode 100644 index 0000000000000..dbe3c24cd8ae4 --- /dev/null +++ b/expression/builtin_time_vec_generated.go @@ -0,0 +1,593 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by go generate in expression/generator; DO NOT EDIT. + +package expression + +import ( + "github.com/pingcap/parser/mysql" + "github.com/pingcap/parser/terror" + "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util/chunk" +) + +func (b *builtinAddDatetimeAndDurationSig) vecEvalTime(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + if err := b.args[0].VecEvalTime(b.ctx, input, result); err != nil { + return err + } + buf0 := result + + buf1, err := b.bufAllocator.get(types.ETDuration, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf1) + if err := b.args[1].VecEvalDuration(b.ctx, input, buf1); err != nil { + return err + } + + result.MergeNulls(buf1) + + arg0s := buf0.Times() + + arg1s := buf1.GoDurations() + + resultSlice := result.Times() + + for i := 0; i < n; i++ { + + if result.IsNull(i) { + continue + } + + // get arg0 & arg1 + + arg0 := arg0s[i] + + arg1 := arg1s[i] + + // calculate + + output, err := arg0.Add(b.ctx.GetSessionVars().StmtCtx, types.Duration{Duration: arg1, Fsp: -1}) + if err != nil { + return err + } + + // commit result + + resultSlice[i] = output + + } + return nil +} + +func (b *builtinAddDatetimeAndDurationSig) vectorized() bool { + return true +} + +func (b *builtinAddDatetimeAndStringSig) vecEvalTime(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + if err := b.args[0].VecEvalTime(b.ctx, input, result); err != nil { + return err + } + buf0 := result + + buf1, err := b.bufAllocator.get(types.ETString, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf1) + if err := b.args[1].VecEvalString(b.ctx, input, buf1); err != nil { + return err + } + + result.MergeNulls(buf1) + + arg0s := buf0.Times() + + resultSlice := result.Times() + + for i := 0; i < n; i++ { + + if result.IsNull(i) { + continue + } + + // get arg0 & arg1 + + arg0 := arg0s[i] + + arg1 := buf1.GetString(i) + + // calculate + + if !isDuration(arg1) { + result.SetNull(i, true) // fixed: true + continue + } + sc := b.ctx.GetSessionVars().StmtCtx + arg1Duration, err := types.ParseDuration(sc, arg1, types.GetFsp(arg1)) + if err != nil { + if terror.ErrorEqual(err, types.ErrTruncatedWrongVal) { + sc.AppendWarning(err) + result.SetNull(i, true) // fixed: true + continue + } + return err + } + + output, err := arg0.Add(sc, arg1Duration) + if err != nil { + return err + } + + // commit result + + resultSlice[i] = output + + } + return nil +} + +func (b *builtinAddDatetimeAndStringSig) vectorized() bool { + return true +} + +func (b *builtinAddDurationAndDurationSig) vecEvalDuration(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + if err := b.args[0].VecEvalDuration(b.ctx, input, result); err != nil { + return err + } + buf0 := result + + buf1, err := b.bufAllocator.get(types.ETDuration, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf1) + if err := b.args[1].VecEvalDuration(b.ctx, input, buf1); err != nil { + return err + } + + result.MergeNulls(buf1) + + arg0s := buf0.GoDurations() + + arg1s := buf1.GoDurations() + + resultSlice := result.GoDurations() + + for i := 0; i < n; i++ { + + if result.IsNull(i) { + continue + } + + // get arg0 & arg1 + + arg0 := arg0s[i] + + arg1 := arg1s[i] + + // calculate + + output, err := types.AddDuration(arg0, arg1) + if err != nil { + return err + } + + // commit result + + resultSlice[i] = output + + } + return nil +} + +func (b *builtinAddDurationAndDurationSig) vectorized() bool { + return true +} + +func (b *builtinAddDurationAndStringSig) vecEvalDuration(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + if err := b.args[0].VecEvalDuration(b.ctx, input, result); err != nil { + return err + } + buf0 := result + + buf1, err := b.bufAllocator.get(types.ETString, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf1) + if err := b.args[1].VecEvalString(b.ctx, input, buf1); err != nil { + return err + } + + result.MergeNulls(buf1) + + arg0s := buf0.GoDurations() + + resultSlice := result.GoDurations() + + for i := 0; i < n; i++ { + + if result.IsNull(i) { + continue + } + + // get arg0 & arg1 + + arg0 := arg0s[i] + + arg1 := buf1.GetString(i) + + // calculate + + if !isDuration(arg1) { + result.SetNull(i, true) // fixed: true + continue + } + sc := b.ctx.GetSessionVars().StmtCtx + arg1Duration, err := types.ParseDuration(sc, arg1, types.GetFsp(arg1)) + if err != nil { + if terror.ErrorEqual(err, types.ErrTruncatedWrongVal) { + sc.AppendWarning(err) + result.SetNull(i, true) // fixed: true + continue + } + return err + } + + output, err := types.AddDuration(arg0, arg1Duration.Duration) + if err != nil { + return err + } + + // commit result + + resultSlice[i] = output + + } + return nil +} + +func (b *builtinAddDurationAndStringSig) vectorized() bool { + return true +} + +func (b *builtinAddStringAndDurationSig) vecEvalString(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + buf0, err := b.bufAllocator.get(types.ETString, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf0) + if err := b.args[0].VecEvalString(b.ctx, input, buf0); err != nil { + return err + } + + buf1, err := b.bufAllocator.get(types.ETDuration, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf1) + if err := b.args[1].VecEvalDuration(b.ctx, input, buf1); err != nil { + return err + } + + arg1s := buf1.GoDurations() + + for i := 0; i < n; i++ { + + if buf0.IsNull(i) || buf1.IsNull(i) { + result.AppendNull() + continue + } + + // get arg0 & arg1 + + arg0 := buf0.GetString(i) + + arg1 := arg1s[i] + + // calculate + + sc := b.ctx.GetSessionVars().StmtCtx + fsp1 := int8(b.args[1].GetType().Decimal) + arg1Duration := types.Duration{Duration: arg1, Fsp: fsp1} + + var output string + if isDuration(arg0) { + output, err = strDurationAddDuration(sc, arg0, arg1Duration) + if err != nil { + if terror.ErrorEqual(err, types.ErrTruncatedWrongVal) { + sc.AppendWarning(err) + result.AppendNull() // fixed: false + continue + } + return err + } + } else { + output, err = strDatetimeAddDuration(sc, arg0, arg1Duration) + if err != nil { + return err + } + } + + // commit result + + result.AppendString(output) + + } + return nil +} + +func (b *builtinAddStringAndDurationSig) vectorized() bool { + return true +} + +func (b *builtinAddStringAndStringSig) vecEvalString(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + buf0, err := b.bufAllocator.get(types.ETString, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf0) + if err := b.args[0].VecEvalString(b.ctx, input, buf0); err != nil { + return err + } + + arg1Type := b.args[1].GetType() + if mysql.HasBinaryFlag(arg1Type.Flag) { + for i := 0; i < n; i++ { + result.AppendNull() + } + return nil + } + + buf1, err := b.bufAllocator.get(types.ETString, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf1) + if err := b.args[1].VecEvalString(b.ctx, input, buf1); err != nil { + return err + } + + for i := 0; i < n; i++ { + + if buf0.IsNull(i) || buf1.IsNull(i) { + result.AppendNull() + continue + } + + // get arg0 & arg1 + + arg0 := buf0.GetString(i) + + arg1 := buf1.GetString(i) + + // calculate + + if !isDuration(arg1) { + result.AppendNull() // fixed: false + continue + } + sc := b.ctx.GetSessionVars().StmtCtx + arg1Duration, err := types.ParseDuration(sc, arg1, getFsp4TimeAddSub(arg1)) + if err != nil { + if terror.ErrorEqual(err, types.ErrTruncatedWrongVal) { + sc.AppendWarning(err) + result.AppendNull() // fixed: false + continue + } + return err + } + + var output string + if isDuration(arg0) { + output, err = strDurationAddDuration(sc, arg0, arg1Duration) + if err != nil { + if terror.ErrorEqual(err, types.ErrTruncatedWrongVal) { + sc.AppendWarning(err) + result.AppendNull() // fixed: false + continue + } + return err + } + } else { + output, err = strDatetimeAddDuration(sc, arg0, arg1Duration) + if err != nil { + return err + } + } + + // commit result + + result.AppendString(output) + + } + return nil +} + +func (b *builtinAddStringAndStringSig) vectorized() bool { + return true +} + +func (b *builtinAddDateAndDurationSig) vecEvalString(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + buf0, err := b.bufAllocator.get(types.ETDuration, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf0) + if err := b.args[0].VecEvalDuration(b.ctx, input, buf0); err != nil { + return err + } + + buf1, err := b.bufAllocator.get(types.ETDuration, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf1) + if err := b.args[1].VecEvalDuration(b.ctx, input, buf1); err != nil { + return err + } + + arg0s := buf0.GoDurations() + + arg1s := buf1.GoDurations() + + for i := 0; i < n; i++ { + + if buf0.IsNull(i) || buf1.IsNull(i) { + result.AppendNull() + continue + } + + // get arg0 & arg1 + + arg0 := arg0s[i] + + arg1 := arg1s[i] + + // calculate + + fsp0 := int8(b.args[0].GetType().Decimal) + fsp1 := int8(b.args[1].GetType().Decimal) + arg1Duration := types.Duration{Duration: arg1, Fsp: fsp1} + sum, err := types.Duration{Duration: arg0, Fsp: fsp0}.Add(arg1Duration) + if err != nil { + return err + } + output := sum.String() + + // commit result + + result.AppendString(output) + + } + return nil +} + +func (b *builtinAddDateAndDurationSig) vectorized() bool { + return true +} + +func (b *builtinAddDateAndStringSig) vecEvalString(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + buf0, err := b.bufAllocator.get(types.ETDuration, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf0) + if err := b.args[0].VecEvalDuration(b.ctx, input, buf0); err != nil { + return err + } + + buf1, err := b.bufAllocator.get(types.ETString, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf1) + if err := b.args[1].VecEvalString(b.ctx, input, buf1); err != nil { + return err + } + + arg0s := buf0.GoDurations() + + for i := 0; i < n; i++ { + + if buf0.IsNull(i) || buf1.IsNull(i) { + result.AppendNull() + continue + } + + // get arg0 & arg1 + + arg0 := arg0s[i] + + arg1 := buf1.GetString(i) + + // calculate + + if !isDuration(arg1) { + result.AppendNull() // fixed: false + continue + } + sc := b.ctx.GetSessionVars().StmtCtx + arg1Duration, err := types.ParseDuration(sc, arg1, getFsp4TimeAddSub(arg1)) + if err != nil { + if terror.ErrorEqual(err, types.ErrTruncatedWrongVal) { + sc.AppendWarning(err) + result.AppendNull() // fixed: false + continue + } + return err + } + + fsp0 := int8(b.args[0].GetType().Decimal) + sum, err := types.Duration{Duration: arg0, Fsp: fsp0}.Add(arg1Duration) + if err != nil { + return err + } + output := sum.String() + + // commit result + + result.AppendString(output) + + } + return nil +} + +func (b *builtinAddDateAndStringSig) vectorized() bool { + return true +} + +func (b *builtinAddTimeDateTimeNullSig) vecEvalTime(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + result.ResizeTime(n, true) + + return nil +} + +func (b *builtinAddTimeStringNullSig) vecEvalString(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + for i := 0; i < n; i++ { + result.AppendNull() + } + + return nil +} + +func (b *builtinAddTimeDurationNullSig) vecEvalDuration(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + + result.ResizeGoDuration(n, true) + + return nil +} diff --git a/expression/builtin_time_vec_generated_test.go b/expression/builtin_time_vec_generated_test.go new file mode 100644 index 0000000000000..0ff56fe9422cf --- /dev/null +++ b/expression/builtin_time_vec_generated_test.go @@ -0,0 +1,182 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by go generate in expression/generator; DO NOT EDIT. + +package expression + +import ( + "testing" + + . "github.com/pingcap/check" + "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/types" +) + +type gener struct { + defaultGener +} + +func (g gener) gen() interface{} { + result := g.defaultGener.gen() + if _, ok := result.(string); ok { + dg := &defaultGener{eType: types.ETDuration, nullRation: 0} + d := dg.gen().(types.Duration) + if int8(d.Duration)%2 == 0 { + d.Fsp = 0 + } else { + d.Fsp = 1 + } + result = d.String() + } + return result +} + +var vecBuiltinTimeGeneratedCases = map[string][]vecExprBenchCase{ + + ast.AddTime: { + // builtinAddDatetimeAndDurationSig + { + retEvalType: types.ETDatetime, + childrenTypes: []types.EvalType{types.ETDatetime, types.ETDuration}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETDatetime, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETDuration, nullRation: 0.2}}, + }, + }, + // builtinAddDatetimeAndStringSig + { + retEvalType: types.ETDatetime, + childrenTypes: []types.EvalType{types.ETDatetime, types.ETString}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETDatetime, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETString, nullRation: 0.2}}, + }, + }, + // builtinAddDurationAndDurationSig + { + retEvalType: types.ETDuration, + childrenTypes: []types.EvalType{types.ETDuration, types.ETDuration}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETDuration, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETDuration, nullRation: 0.2}}, + }, + }, + // builtinAddDurationAndStringSig + { + retEvalType: types.ETDuration, + childrenTypes: []types.EvalType{types.ETDuration, types.ETString}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETDuration, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETString, nullRation: 0.2}}, + }, + }, + // builtinAddStringAndDurationSig + { + retEvalType: types.ETString, + childrenTypes: []types.EvalType{types.ETString, types.ETDuration}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETString, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETDuration, nullRation: 0.2}}, + }, + }, + // builtinAddStringAndStringSig + { + retEvalType: types.ETString, + childrenTypes: []types.EvalType{types.ETString, types.ETString}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETString, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETString, nullRation: 0.2}}, + }, + }, + // builtinAddDateAndDurationSig + { + retEvalType: types.ETString, + childrenTypes: []types.EvalType{types.ETDuration, types.ETDuration}, + + childrenFieldTypes: []*types.FieldType{types.NewFieldType(mysql.TypeDate), types.NewFieldType(mysql.TypeDuration)}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETDuration, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETDuration, nullRation: 0.2}}, + }, + }, + // builtinAddDateAndStringSig + { + retEvalType: types.ETString, + childrenTypes: []types.EvalType{types.ETDuration, types.ETString}, + + childrenFieldTypes: []*types.FieldType{types.NewFieldType(mysql.TypeDate), types.NewFieldType(mysql.TypeString)}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETDuration, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETString, nullRation: 0.2}}, + }, + }, + // builtinAddTimeDateTimeNullSig + { + retEvalType: types.ETDatetime, + childrenTypes: []types.EvalType{types.ETDatetime, types.ETDatetime}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETDatetime, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETDatetime, nullRation: 0.2}}, + }, + }, + // builtinAddTimeStringNullSig + { + retEvalType: types.ETString, + childrenTypes: []types.EvalType{types.ETDatetime, types.ETDatetime}, + + childrenFieldTypes: []*types.FieldType{types.NewFieldType(mysql.TypeDate), types.NewFieldType(mysql.TypeDatetime)}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETDatetime, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETDatetime, nullRation: 0.2}}, + }, + }, + // builtinAddTimeDurationNullSig + { + retEvalType: types.ETDuration, + childrenTypes: []types.EvalType{types.ETDuration, types.ETDatetime}, + + geners: []dataGenerator{ + gener{defaultGener{eType: types.ETDuration, nullRation: 0.2}}, + gener{defaultGener{eType: types.ETDatetime, nullRation: 0.2}}, + }, + }, + }, +} + +func (s *testEvaluatorSuite) TestVectorizedBuiltinTimeEvalOneVecGenerated(c *C) { + testVectorizedEvalOneVec(c, vecBuiltinTimeGeneratedCases) +} + +func (s *testEvaluatorSuite) TestVectorizedBuiltinTimeFuncGenerated(c *C) { + testVectorizedBuiltinFunc(c, vecBuiltinTimeGeneratedCases) +} + +func BenchmarkVectorizedBuiltinTimeEvalOneVecGenerated(b *testing.B) { + benchmarkVectorizedEvalOneVec(b, vecBuiltinTimeGeneratedCases) +} + +func BenchmarkVectorizedBuiltinTimeFuncGenerated(b *testing.B) { + benchmarkVectorizedBuiltinFunc(b, vecBuiltinTimeGeneratedCases) +} diff --git a/expression/generator/helper/helper.go b/expression/generator/helper/helper.go new file mode 100644 index 0000000000000..934e21995c8f3 --- /dev/null +++ b/expression/generator/helper/helper.go @@ -0,0 +1,46 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package helper + +// TypeContext is the template context for each "github.com/pingcap/tidb/types".EvalType . +type TypeContext struct { + // Describe the name of "github.com/pingcap/tidb/types".ET{{ .ETName }} . + ETName string + // Describe the name of "github.com/pingcap/tidb/expression".VecExpr.VecEval{{ .TypeName }} . + TypeName string + // Describe the name of "github.com/pingcap/tidb/util/chunk".*Column.Append{{ .TypeNameInColumn }}, + // Resize{{ .TypeNameInColumn }}, Reserve{{ .TypeNameInColumn }}, Get{{ .TypeNameInColumn }} and + // {{ .TypeNameInColumn }}s. + // If undefined, it's same as TypeName. + TypeNameInColumn string + // Same as "github.com/pingcap/tidb/util/chunk".getFixedLen() . + Fixed bool +} + +var ( + // TypeInt represents the template context of types.ETInt . + TypeInt = TypeContext{ETName: "Int", TypeName: "Int", TypeNameInColumn: "Int64", Fixed: true} + // TypeReal represents the template context of types.ETReal . + TypeReal = TypeContext{ETName: "Real", TypeName: "Real", TypeNameInColumn: "Float64", Fixed: true} + // TypeDecimal represents the template context of types.ETDecimal . + TypeDecimal = TypeContext{ETName: "Decimal", TypeName: "Decimal", TypeNameInColumn: "Decimal", Fixed: true} + // TypeString represents the template context of types.ETString . + TypeString = TypeContext{ETName: "String", TypeName: "String", TypeNameInColumn: "String", Fixed: false} + // TypeDatetime represents the template context of types.ETDatetime . + TypeDatetime = TypeContext{ETName: "Datetime", TypeName: "Time", TypeNameInColumn: "Time", Fixed: true} + // TypeDuration represents the template context of types.ETDuration . + TypeDuration = TypeContext{ETName: "Duration", TypeName: "Duration", TypeNameInColumn: "GoDuration", Fixed: true} + // TypeJSON represents the template context of types.ETJson . + TypeJSON = TypeContext{ETName: "Json", TypeName: "JSON", TypeNameInColumn: "JSON", Fixed: false} +) diff --git a/expression/generator/time_vec.go b/expression/generator/time_vec.go new file mode 100644 index 0000000000000..fcb0e8adca11b --- /dev/null +++ b/expression/generator/time_vec.go @@ -0,0 +1,410 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build ignore + +package main + +import ( + "bytes" + "flag" + "go/format" + "io/ioutil" + "log" + "path/filepath" + "text/template" + + . "github.com/pingcap/tidb/expression/generator/helper" +) + +const header = `// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by go generate in expression/generator; DO NOT EDIT. + +package expression +` + +const imports = `import ( + "github.com/pingcap/parser/mysql" + "github.com/pingcap/parser/terror" + "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util/chunk" +)` + +// TODO: +// builtinAddTimeDateTimeNullSig.evalTime +// builtinAddTimeStringNullSig.evalString +// builtinAddTimeDurationNullSig.evalDuration +// builtinAddTimeStringNullSig.evalString +var addTime = template.Must(template.New("").Parse(` +{{ define "SetNull" }}{{if .Output.Fixed}}result.SetNull(i, true){{else}}result.AppendNull(){{end}} // fixed: {{.Output.Fixed }}{{ end }} +{{ define "ConvertStringToDuration" }} + if !isDuration(arg1) { + {{ template "SetNull" . }} + continue + } + sc := b.ctx.GetSessionVars().StmtCtx + arg1Duration, err := types.ParseDuration(sc, arg1, {{if eq .Output.TypeName "String"}}getFsp4TimeAddSub{{else}}types.GetFsp{{end}}(arg1)) + if err != nil { + if terror.ErrorEqual(err, types.ErrTruncatedWrongVal) { + sc.AppendWarning(err) + {{ template "SetNull" . }} + continue + } + return err + } +{{ end }} +{{ define "strDurationAddDuration" }} + var output string + if isDuration(arg0) { + output, err = strDurationAddDuration(sc, arg0, arg1Duration) + if err != nil { + if terror.ErrorEqual(err, types.ErrTruncatedWrongVal) { + sc.AppendWarning(err) + {{ template "SetNull" . }} + continue + } + return err + } + } else { + output, err = strDatetimeAddDuration(sc, arg0, arg1Duration) + if err != nil { + return err + } + } +{{ end }} + +{{ range . }} +{{ if .AllNull}} +func (b *{{.SigName}}) vecEval{{ .Output.TypeName }}(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() + {{ if .Output.Fixed }} + result.Resize{{ .Output.TypeNameInColumn }}(n, true) + {{ else }} + for i := 0; i < n; i++ { result.AppendNull() } + {{ end }} + return nil +} +{{ else }} +func (b *{{.SigName}}) vecEval{{ .Output.TypeName }}(input *chunk.Chunk, result *chunk.Column) error { + n := input.NumRows() +{{ $reuse := (and (eq .TypeA.TypeName .Output.TypeName) .TypeA.Fixed) }} +{{ if $reuse }} + if err := b.args[0].VecEval{{ .TypeA.TypeName }}(b.ctx, input, result); err != nil { + return err + } + buf0 := result +{{ else }} + buf0, err := b.bufAllocator.get(types.ET{{.TypeA.ETName}}, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf0) + if err := b.args[0].VecEval{{ .TypeA.TypeName }}(b.ctx, input, buf0); err != nil { + return err + } +{{ end }} + +{{ if eq .SigName "builtinAddStringAndStringSig" }} + arg1Type := b.args[1].GetType() + if mysql.HasBinaryFlag(arg1Type.Flag) { + for i := 0; i < n; i++ { + result.AppendNull() + } + return nil + } +{{ end }} + + buf1, err := b.bufAllocator.get(types.ET{{.TypeB.ETName}}, n) + if err != nil { + return err + } + defer b.bufAllocator.put(buf1) + if err := b.args[1].VecEval{{ .TypeB.TypeName }}(b.ctx, input, buf1); err != nil { + return err + } + +{{ if $reuse }} + result.MergeNulls(buf1) +{{ else if .Output.Fixed}} + result.Resize{{ .Output.TypeNameInColumn }}(n, false) + result.MergeNulls(buf0, buf1) +{{ end }} + +{{ if .TypeA.Fixed }} + arg0s := buf0.{{.TypeA.TypeNameInColumn}}s() +{{ end }} +{{ if .TypeB.Fixed }} + arg1s := buf1.{{.TypeB.TypeNameInColumn}}s() +{{ end }} +{{ if .Output.Fixed }} + resultSlice := result.{{.Output.TypeNameInColumn}}s() +{{ end }} + for i := 0; i < n; i++ { + {{ if .Output.Fixed }} + if result.IsNull(i) { + continue + } + {{ else }} + if buf0.IsNull(i) || buf1.IsNull(i) { + result.AppendNull() + continue + } + {{ end }} + + // get arg0 & arg1 + {{ if .TypeA.Fixed }} + arg0 := arg0s[i] + {{ else }} + arg0 := buf0.Get{{ .TypeA.TypeNameInColumn }}(i) + {{ end }} + {{ if .TypeB.Fixed }} + arg1 := arg1s[i] + {{ else }} + arg1 := buf1.Get{{ .TypeB.TypeNameInColumn }}(i) + {{ end }} + + // calculate + {{ if eq .SigName "builtinAddDatetimeAndDurationSig" }} + output, err := arg0.Add(b.ctx.GetSessionVars().StmtCtx, types.Duration{Duration: arg1, Fsp: -1}) + if err != nil { + return err + } + {{ else if eq .SigName "builtinAddDatetimeAndStringSig" }} + {{ template "ConvertStringToDuration" . }} + output, err := arg0.Add(sc, arg1Duration) + if err != nil { + return err + } + {{ else if eq .SigName "builtinAddDurationAndDurationSig" }} + output, err := types.AddDuration(arg0, arg1) + if err != nil { + return err + } + {{ else if eq .SigName "builtinAddDurationAndStringSig" }} + {{ template "ConvertStringToDuration" . }} + output, err := types.AddDuration(arg0, arg1Duration.Duration) + if err != nil { + return err + } + {{ else if eq .SigName "builtinAddStringAndDurationSig" }} + sc := b.ctx.GetSessionVars().StmtCtx + fsp1 := int8(b.args[1].GetType().Decimal) + arg1Duration := types.Duration{Duration: arg1, Fsp: fsp1} + {{ template "strDurationAddDuration" . }} + {{ else if eq .SigName "builtinAddStringAndStringSig" }} + {{ template "ConvertStringToDuration" . }} + {{ template "strDurationAddDuration" . }} + {{ else if eq .SigName "builtinAddDateAndDurationSig" }} + fsp0 := int8(b.args[0].GetType().Decimal) + fsp1 := int8(b.args[1].GetType().Decimal) + arg1Duration := types.Duration{Duration: arg1, Fsp: fsp1} + sum, err := types.Duration{Duration: arg0, Fsp: fsp0}.Add(arg1Duration) + if err != nil { + return err + } + output := sum.String() + {{ else if eq .SigName "builtinAddDateAndStringSig" }} + {{ template "ConvertStringToDuration" . }} + fsp0 := int8(b.args[0].GetType().Decimal) + sum, err := types.Duration{Duration: arg0, Fsp: fsp0}.Add(arg1Duration) + if err != nil { + return err + } + output := sum.String() + {{ end }} + + // commit result + {{ if .Output.Fixed }} + resultSlice[i] = output + {{ else }} + result.Append{{ .Output.TypeNameInColumn }}(output) + {{ end }} + } + return nil +} + +func (b *{{.SigName}}) vectorized() bool { + return true +} +{{ end }}{{/* if .AllNull */}} +{{ end }}{{/* range */}} +`)) + +var testFile = template.Must(template.New("").Parse(` +import ( + "testing" + + . "github.com/pingcap/check" + "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/types" +) + +type gener struct { + defaultGener +} + +func (g gener) gen() interface{} { + result := g.defaultGener.gen() + if _, ok := result.(string); ok { + dg := &defaultGener{eType: types.ETDuration, nullRation: 0} + d := dg.gen().(types.Duration) + if int8(d.Duration)%2 == 0 { + d.Fsp = 0 + } else { + d.Fsp = 1 + } + result = d.String() + } + return result +} + +{{/* Add more test cases here if we have more functions in this file */}} +var vecBuiltin{{.Category}}GeneratedCases = map[string][]vecExprBenchCase{ +{{ range .Functions }} + ast.{{.FuncName}}: { + {{ range .Sigs }} // {{ .SigName }} + { + retEvalType: types.ET{{ .Output.ETName }}, + childrenTypes: []types.EvalType{types.ET{{ .TypeA.ETName }}, types.ET{{ .TypeB.ETName }}}, + {{ if ne .FieldTypeA "" }} + childrenFieldTypes: []*types.FieldType{types.NewFieldType(mysql.Type{{.FieldTypeA}}), types.NewFieldType(mysql.Type{{.FieldTypeB}})}, + {{ end }} + geners: []dataGenerator{ + gener{defaultGener{eType: types.ET{{.TypeA.ETName}}, nullRation: 0.2}}, + gener{defaultGener{eType: types.ET{{.TypeB.ETName}}, nullRation: 0.2}}, + }, + }, + {{ end }} +{{ end }} + }, +} + +func (s *testEvaluatorSuite) TestVectorizedBuiltin{{.Category}}EvalOneVecGenerated(c *C) { + testVectorizedEvalOneVec(c, vecBuiltin{{.Category}}GeneratedCases) +} + +func (s *testEvaluatorSuite) TestVectorizedBuiltin{{.Category}}FuncGenerated(c *C) { + testVectorizedBuiltinFunc(c, vecBuiltin{{.Category}}GeneratedCases) +} + +func BenchmarkVectorizedBuiltin{{.Category}}EvalOneVecGenerated(b *testing.B) { + benchmarkVectorizedEvalOneVec(b, vecBuiltin{{.Category}}GeneratedCases) +} + +func BenchmarkVectorizedBuiltin{{.Category}}FuncGenerated(b *testing.B) { + benchmarkVectorizedBuiltinFunc(b, vecBuiltin{{.Category}}GeneratedCases) +} +`)) + +var addTimeSigsTmpl = []sig{ + {SigName: "builtinAddDatetimeAndDurationSig", TypeA: TypeDatetime, TypeB: TypeDuration, Output: TypeDatetime}, + {SigName: "builtinAddDatetimeAndStringSig", TypeA: TypeDatetime, TypeB: TypeString, Output: TypeDatetime}, + {SigName: "builtinAddDurationAndDurationSig", TypeA: TypeDuration, TypeB: TypeDuration, Output: TypeDuration}, + {SigName: "builtinAddDurationAndStringSig", TypeA: TypeDuration, TypeB: TypeString, Output: TypeDuration}, + {SigName: "builtinAddStringAndDurationSig", TypeA: TypeString, TypeB: TypeDuration, Output: TypeString}, + {SigName: "builtinAddStringAndStringSig", TypeA: TypeString, TypeB: TypeString, Output: TypeString}, + {SigName: "builtinAddDateAndDurationSig", TypeA: TypeDuration, TypeB: TypeDuration, Output: TypeString, FieldTypeA: "Date", FieldTypeB: "Duration"}, + {SigName: "builtinAddDateAndStringSig", TypeA: TypeDuration, TypeB: TypeString, Output: TypeString, FieldTypeA: "Date", FieldTypeB: "String"}, + + {SigName: "builtinAddTimeDateTimeNullSig", TypeA: TypeDatetime, TypeB: TypeDatetime, Output: TypeDatetime, AllNull: true}, + {SigName: "builtinAddTimeStringNullSig", TypeA: TypeDatetime, TypeB: TypeDatetime, Output: TypeString, AllNull: true, FieldTypeA: "Date", FieldTypeB: "Datetime"}, + {SigName: "builtinAddTimeDurationNullSig", TypeA: TypeDuration, TypeB: TypeDatetime, Output: TypeDuration, AllNull: true}, +} + +type sig struct { + SigName string + TypeA, TypeB, Output TypeContext + FieldTypeA, FieldTypeB string // Optional + AllNull bool +} + +type function struct { + FuncName string + Sigs []sig +} + +var tmplVal = struct { + Category string + Functions []function +}{ + Category: "Time", + Functions: []function{ + {FuncName: "AddTime", Sigs: addTimeSigsTmpl}, + }, +} + +func generateDotGo(fileName string) error { + w := new(bytes.Buffer) + w.WriteString(header) + w.WriteString("\n") + w.WriteString(imports) + err := addTime.Execute(w, addTimeSigsTmpl) + if err != nil { + return err + } + data, err := format.Source(w.Bytes()) + if err != nil { + log.Println("[Warn]", fileName+": gofmt failed", err) + data = w.Bytes() // write original data for debugging + } + return ioutil.WriteFile(fileName, data, 0644) +} + +func generateTestDotGo(fileName string) error { + w := new(bytes.Buffer) + w.WriteString(header) + err := testFile.Execute(w, tmplVal) + if err != nil { + return err + } + data, err := format.Source(w.Bytes()) + if err != nil { + log.Println("[Warn]", fileName+": gofmt failed", err) + data = w.Bytes() // write original data for debugging + } + return ioutil.WriteFile(fileName, data, 0644) +} + +// generateOneFile generate one xxx.go file and the associated xxx_test.go file. +func generateOneFile(fileNamePrefix string) (err error) { + + err = generateDotGo(fileNamePrefix + ".go") + if err != nil { + return + } + err = generateTestDotGo(fileNamePrefix + "_test.go") + return +} + +func main() { + flag.Parse() + var err error + outputDir := "." + err = generateOneFile(filepath.Join(outputDir, "builtin_time_vec_generated")) + if err != nil { + log.Fatalln("generateOneFile", err) + } +} diff --git a/types/overflow.go b/types/overflow.go index 6253357cbe047..07bdc789e528b 100644 --- a/types/overflow.go +++ b/types/overflow.go @@ -16,6 +16,7 @@ package types import ( "fmt" "math" + "time" "github.com/pingcap/errors" ) @@ -38,6 +39,16 @@ func AddInt64(a int64, b int64) (int64, error) { return a + b, nil } +// AddDuration adds time.Duration a and b if no overflow, otherwise returns error. +func AddDuration(a time.Duration, b time.Duration) (time.Duration, error) { + if (a > 0 && b > 0 && math.MaxInt64-a < b) || + (a < 0 && b < 0 && math.MinInt64-a > b) { + return 0, ErrOverflow.GenWithStackByArgs("BIGINT", fmt.Sprintf("(%d, %d)", int64(a), int64(b))) + } + + return a + b, nil +} + // AddInteger adds uint64 a and int64 b and returns uint64 if no overflow error. func AddInteger(a uint64, b int64) (uint64, error) { if b >= 0 { diff --git a/types/overflow_test.go b/types/overflow_test.go index 43d6d84a88c9a..27b7c2f80dd70 100644 --- a/types/overflow_test.go +++ b/types/overflow_test.go @@ -15,6 +15,7 @@ package types import ( "math" + "time" . "github.com/pingcap/check" "github.com/pingcap/tidb/util/testleak" @@ -69,6 +70,12 @@ func (s *testOverflowSuite) TestAdd(c *C) { } else { c.Assert(ret, Equals, t.ret) } + ret2, err := AddDuration(time.Duration(t.lsh), time.Duration(t.rsh)) + if t.overflow { + c.Assert(err, NotNil) + } else { + c.Assert(ret2, Equals, time.Duration(t.ret)) + } } tblInt := []struct {