You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
487 lines
15 KiB
487 lines
15 KiB
// Copyright 2017 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 expression
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"github.com/pingcap/parser/ast"
|
|
"github.com/pingcap/parser/mysql"
|
|
"github.com/pingcap/tidb/sessionctx"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util/chunk"
|
|
)
|
|
|
|
// Vectorizable checks whether a list of expressions can employ vectorized execution.
|
|
func Vectorizable(exprs []Expression) bool {
|
|
for _, expr := range exprs {
|
|
if HasGetSetVarFunc(expr) {
|
|
return false
|
|
}
|
|
}
|
|
return checkSequenceFunction(exprs)
|
|
}
|
|
|
|
// checkSequenceFunction indicates whether the exprs can be evaluated as a vector.
|
|
// When two or more of this three(nextval, lastval, setval) exists in exprs list and one of them is nextval, it should be eval row by row.
|
|
func checkSequenceFunction(exprs []Expression) bool {
|
|
var (
|
|
nextval int
|
|
lastval int
|
|
setval int
|
|
)
|
|
for _, expr := range exprs {
|
|
scalaFunc, ok := expr.(*ScalarFunction)
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch scalaFunc.FuncName.L {
|
|
case ast.NextVal:
|
|
nextval++
|
|
case ast.LastVal:
|
|
lastval++
|
|
case ast.SetVal:
|
|
setval++
|
|
}
|
|
}
|
|
// case1: nextval && other sequence function.
|
|
// case2: more than one nextval.
|
|
if (nextval > 0 && (lastval > 0 || setval > 0)) || (nextval > 1) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// HasGetSetVarFunc checks whether an expression contains SetVar/GetVar function.
|
|
func HasGetSetVarFunc(expr Expression) bool {
|
|
scalaFunc, ok := expr.(*ScalarFunction)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if scalaFunc.FuncName.L == ast.SetVar {
|
|
return true
|
|
}
|
|
if scalaFunc.FuncName.L == ast.GetVar {
|
|
return true
|
|
}
|
|
for _, arg := range scalaFunc.GetArgs() {
|
|
if HasGetSetVarFunc(arg) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HasAssignSetVarFunc checks whether an expression contains SetVar function and assign a value
|
|
func HasAssignSetVarFunc(expr Expression) bool {
|
|
scalaFunc, ok := expr.(*ScalarFunction)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if scalaFunc.FuncName.L == ast.SetVar {
|
|
for _, arg := range scalaFunc.GetArgs() {
|
|
if _, ok := arg.(*ScalarFunction); ok {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
for _, arg := range scalaFunc.GetArgs() {
|
|
if HasAssignSetVarFunc(arg) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// VectorizedExecute evaluates a list of expressions column by column and append their results to "output" Chunk.
|
|
func VectorizedExecute(ctx sessionctx.Context, exprs []Expression, iterator *chunk.Iterator4Chunk, output *chunk.Chunk) error {
|
|
for colID, expr := range exprs {
|
|
err := evalOneColumn(ctx, expr, iterator, output, colID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func evalOneVec(ctx sessionctx.Context, expr Expression, input *chunk.Chunk, output *chunk.Chunk, colIdx int) error {
|
|
ft := expr.GetType()
|
|
result := output.Column(colIdx)
|
|
switch ft.EvalType() {
|
|
case types.ETInt:
|
|
if err := expr.VecEvalInt(ctx, input, result); err != nil {
|
|
return err
|
|
}
|
|
if ft.Tp == mysql.TypeBit {
|
|
i64s := result.Int64s()
|
|
buf := chunk.NewColumn(ft, input.NumRows())
|
|
buf.ReserveBytes(input.NumRows())
|
|
uintBuf := make([]byte, 8)
|
|
for i := range i64s {
|
|
if result.IsNull(i) {
|
|
buf.AppendNull()
|
|
} else {
|
|
buf.AppendBytes(strconv.AppendUint(uintBuf[:0], uint64(i64s[i]), 10))
|
|
}
|
|
}
|
|
// TODO: recycle all old Columns returned here.
|
|
output.SetCol(colIdx, buf)
|
|
} else if mysql.HasUnsignedFlag(ft.Flag) {
|
|
// the underlying memory formats of int64 and uint64 are the same in Golang,
|
|
// so we can do a no-op here.
|
|
}
|
|
case types.ETReal:
|
|
if err := expr.VecEvalReal(ctx, input, result); err != nil {
|
|
return err
|
|
}
|
|
if ft.Tp == mysql.TypeFloat {
|
|
f64s := result.Float64s()
|
|
n := input.NumRows()
|
|
buf := chunk.NewColumn(ft, n)
|
|
buf.ResizeFloat32(n, false)
|
|
f32s := buf.Float32s()
|
|
for i := range f64s {
|
|
if result.IsNull(i) {
|
|
buf.SetNull(i, true)
|
|
} else {
|
|
f32s[i] = float32(f64s[i])
|
|
}
|
|
}
|
|
output.SetCol(colIdx, buf)
|
|
}
|
|
case types.ETDecimal:
|
|
return expr.VecEvalDecimal(ctx, input, result)
|
|
case types.ETDatetime, types.ETTimestamp:
|
|
return expr.VecEvalTime(ctx, input, result)
|
|
case types.ETDuration:
|
|
return expr.VecEvalDuration(ctx, input, result)
|
|
case types.ETJson:
|
|
return expr.VecEvalJSON(ctx, input, result)
|
|
case types.ETString:
|
|
if err := expr.VecEvalString(ctx, input, result); err != nil {
|
|
return err
|
|
}
|
|
if ft.Tp == mysql.TypeEnum {
|
|
n := input.NumRows()
|
|
buf := chunk.NewColumn(ft, n)
|
|
buf.ReserveEnum(n)
|
|
for i := 0; i < n; i++ {
|
|
if result.IsNull(i) {
|
|
buf.AppendNull()
|
|
} else {
|
|
buf.AppendEnum(types.Enum{Value: 0, Name: result.GetString(i)})
|
|
}
|
|
}
|
|
output.SetCol(colIdx, buf)
|
|
} else if ft.Tp == mysql.TypeSet {
|
|
n := input.NumRows()
|
|
buf := chunk.NewColumn(ft, n)
|
|
buf.ReserveSet(n)
|
|
for i := 0; i < n; i++ {
|
|
if result.IsNull(i) {
|
|
buf.AppendNull()
|
|
} else {
|
|
buf.AppendSet(types.Set{Value: 0, Name: result.GetString(i)})
|
|
}
|
|
}
|
|
output.SetCol(colIdx, buf)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func evalOneColumn(ctx sessionctx.Context, expr Expression, iterator *chunk.Iterator4Chunk, output *chunk.Chunk, colID int) (err error) {
|
|
switch fieldType, evalType := expr.GetType(), expr.GetType().EvalType(); evalType {
|
|
case types.ETInt:
|
|
for row := iterator.Begin(); err == nil && row != iterator.End(); row = iterator.Next() {
|
|
err = executeToInt(ctx, expr, fieldType, row, output, colID)
|
|
}
|
|
case types.ETReal:
|
|
for row := iterator.Begin(); err == nil && row != iterator.End(); row = iterator.Next() {
|
|
err = executeToReal(ctx, expr, fieldType, row, output, colID)
|
|
}
|
|
case types.ETDecimal:
|
|
for row := iterator.Begin(); err == nil && row != iterator.End(); row = iterator.Next() {
|
|
err = executeToDecimal(ctx, expr, fieldType, row, output, colID)
|
|
}
|
|
case types.ETDatetime, types.ETTimestamp:
|
|
for row := iterator.Begin(); err == nil && row != iterator.End(); row = iterator.Next() {
|
|
err = executeToDatetime(ctx, expr, fieldType, row, output, colID)
|
|
}
|
|
case types.ETDuration:
|
|
for row := iterator.Begin(); err == nil && row != iterator.End(); row = iterator.Next() {
|
|
err = executeToDuration(ctx, expr, fieldType, row, output, colID)
|
|
}
|
|
case types.ETJson:
|
|
for row := iterator.Begin(); err == nil && row != iterator.End(); row = iterator.Next() {
|
|
err = executeToJSON(ctx, expr, fieldType, row, output, colID)
|
|
}
|
|
case types.ETString:
|
|
for row := iterator.Begin(); err == nil && row != iterator.End(); row = iterator.Next() {
|
|
err = executeToString(ctx, expr, fieldType, row, output, colID)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func evalOneCell(ctx sessionctx.Context, expr Expression, row chunk.Row, output *chunk.Chunk, colID int) (err error) {
|
|
switch fieldType, evalType := expr.GetType(), expr.GetType().EvalType(); evalType {
|
|
case types.ETInt:
|
|
err = executeToInt(ctx, expr, fieldType, row, output, colID)
|
|
case types.ETReal:
|
|
err = executeToReal(ctx, expr, fieldType, row, output, colID)
|
|
case types.ETDecimal:
|
|
err = executeToDecimal(ctx, expr, fieldType, row, output, colID)
|
|
case types.ETDatetime, types.ETTimestamp:
|
|
err = executeToDatetime(ctx, expr, fieldType, row, output, colID)
|
|
case types.ETDuration:
|
|
err = executeToDuration(ctx, expr, fieldType, row, output, colID)
|
|
case types.ETJson:
|
|
err = executeToJSON(ctx, expr, fieldType, row, output, colID)
|
|
case types.ETString:
|
|
err = executeToString(ctx, expr, fieldType, row, output, colID)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func executeToInt(ctx sessionctx.Context, expr Expression, fieldType *types.FieldType, row chunk.Row, output *chunk.Chunk, colID int) error {
|
|
res, isNull, err := expr.EvalInt(ctx, row)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isNull {
|
|
output.AppendNull(colID)
|
|
return nil
|
|
}
|
|
if fieldType.Tp == mysql.TypeBit {
|
|
output.AppendBytes(colID, strconv.AppendUint(make([]byte, 0, 8), uint64(res), 10))
|
|
return nil
|
|
}
|
|
if mysql.HasUnsignedFlag(fieldType.Flag) {
|
|
output.AppendUint64(colID, uint64(res))
|
|
return nil
|
|
}
|
|
output.AppendInt64(colID, res)
|
|
return nil
|
|
}
|
|
|
|
func executeToReal(ctx sessionctx.Context, expr Expression, fieldType *types.FieldType, row chunk.Row, output *chunk.Chunk, colID int) error {
|
|
res, isNull, err := expr.EvalReal(ctx, row)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isNull {
|
|
output.AppendNull(colID)
|
|
return nil
|
|
}
|
|
if fieldType.Tp == mysql.TypeFloat {
|
|
output.AppendFloat32(colID, float32(res))
|
|
return nil
|
|
}
|
|
output.AppendFloat64(colID, res)
|
|
return nil
|
|
}
|
|
|
|
func executeToDecimal(ctx sessionctx.Context, expr Expression, fieldType *types.FieldType, row chunk.Row, output *chunk.Chunk, colID int) error {
|
|
res, isNull, err := expr.EvalDecimal(ctx, row)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isNull {
|
|
output.AppendNull(colID)
|
|
return nil
|
|
}
|
|
output.AppendMyDecimal(colID, res)
|
|
return nil
|
|
}
|
|
|
|
func executeToDatetime(ctx sessionctx.Context, expr Expression, fieldType *types.FieldType, row chunk.Row, output *chunk.Chunk, colID int) error {
|
|
res, isNull, err := expr.EvalTime(ctx, row)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isNull {
|
|
output.AppendNull(colID)
|
|
} else {
|
|
output.AppendTime(colID, res)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func executeToDuration(ctx sessionctx.Context, expr Expression, fieldType *types.FieldType, row chunk.Row, output *chunk.Chunk, colID int) error {
|
|
res, isNull, err := expr.EvalDuration(ctx, row)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isNull {
|
|
output.AppendNull(colID)
|
|
} else {
|
|
output.AppendDuration(colID, res)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func executeToJSON(ctx sessionctx.Context, expr Expression, fieldType *types.FieldType, row chunk.Row, output *chunk.Chunk, colID int) error {
|
|
res, isNull, err := expr.EvalJSON(ctx, row)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isNull {
|
|
output.AppendNull(colID)
|
|
} else {
|
|
output.AppendJSON(colID, res)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func executeToString(ctx sessionctx.Context, expr Expression, fieldType *types.FieldType, row chunk.Row, output *chunk.Chunk, colID int) error {
|
|
res, isNull, err := expr.EvalString(ctx, row)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isNull {
|
|
output.AppendNull(colID)
|
|
} else if fieldType.Tp == mysql.TypeEnum {
|
|
val := types.Enum{Value: uint64(0), Name: res}
|
|
output.AppendEnum(colID, val)
|
|
} else if fieldType.Tp == mysql.TypeSet {
|
|
val := types.Set{Value: uint64(0), Name: res}
|
|
output.AppendSet(colID, val)
|
|
} else {
|
|
output.AppendString(colID, res)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// VectorizedFilter applies a list of filters to a Chunk and
|
|
// returns a bool slice, which indicates whether a row is passed the filters.
|
|
// Filters is executed vectorized.
|
|
func VectorizedFilter(ctx sessionctx.Context, filters []Expression, iterator *chunk.Iterator4Chunk, selected []bool) (_ []bool, err error) {
|
|
selected, _, err = VectorizedFilterConsiderNull(ctx, filters, iterator, selected, nil)
|
|
return selected, err
|
|
}
|
|
|
|
// VectorizedFilterConsiderNull applies a list of filters to a Chunk and
|
|
// returns two bool slices, `selected` indicates whether a row passed the
|
|
// filters, `isNull` indicates whether the result of the filter is null.
|
|
// Filters is executed vectorized.
|
|
func VectorizedFilterConsiderNull(ctx sessionctx.Context, filters []Expression, iterator *chunk.Iterator4Chunk, selected []bool, isNull []bool) ([]bool, []bool, error) {
|
|
// canVectorized used to check whether all of the filters can be vectorized evaluated
|
|
canVectorized := true
|
|
for _, filter := range filters {
|
|
if !filter.Vectorized() {
|
|
canVectorized = false
|
|
break
|
|
}
|
|
}
|
|
|
|
input := iterator.GetChunk()
|
|
sel := input.Sel()
|
|
var err error
|
|
if canVectorized && ctx.GetSessionVars().EnableVectorizedExpression {
|
|
selected, isNull, err = vectorizedFilter(ctx, filters, iterator, selected, isNull)
|
|
} else {
|
|
selected, isNull, err = rowBasedFilter(ctx, filters, iterator, selected, isNull)
|
|
}
|
|
if err != nil || sel == nil {
|
|
return selected, isNull, err
|
|
}
|
|
|
|
// When the input.Sel() != nil, we need to handle the selected slice and input.Sel()
|
|
// Get the index which is not appeared in input.Sel() and set the selected[index] = false
|
|
selectedLength := len(selected)
|
|
unselected := allocZeroSlice(selectedLength)
|
|
defer deallocateZeroSlice(unselected)
|
|
// unselected[i] == 1 means that the i-th row is not selected
|
|
for i := 0; i < selectedLength; i++ {
|
|
unselected[i] = 1
|
|
}
|
|
for _, ind := range sel {
|
|
unselected[ind] = 0
|
|
}
|
|
for i := 0; i < selectedLength; i++ {
|
|
if selected[i] && unselected[i] == 1 {
|
|
selected[i] = false
|
|
}
|
|
}
|
|
return selected, isNull, err
|
|
}
|
|
|
|
// rowBasedFilter filters by row.
|
|
func rowBasedFilter(ctx sessionctx.Context, filters []Expression, iterator *chunk.Iterator4Chunk, selected []bool, isNull []bool) ([]bool, []bool, error) {
|
|
// If input.Sel() != nil, we will call input.SetSel(nil) to clear the sel slice in input chunk.
|
|
// After the function finished, then we reset the sel in input chunk.
|
|
// Then the caller will handle the input.sel and selected slices.
|
|
input := iterator.GetChunk()
|
|
if input.Sel() != nil {
|
|
defer input.SetSel(input.Sel())
|
|
input.SetSel(nil)
|
|
iterator = chunk.NewIterator4Chunk(input)
|
|
}
|
|
|
|
selected = selected[:0]
|
|
for i, numRows := 0, iterator.Len(); i < numRows; i++ {
|
|
selected = append(selected, true)
|
|
}
|
|
if isNull != nil {
|
|
isNull = isNull[:0]
|
|
for i, numRows := 0, iterator.Len(); i < numRows; i++ {
|
|
isNull = append(isNull, false)
|
|
}
|
|
}
|
|
var (
|
|
filterResult int64
|
|
bVal, isNullResult bool
|
|
err error
|
|
)
|
|
for _, filter := range filters {
|
|
isIntType := true
|
|
if filter.GetType().EvalType() != types.ETInt {
|
|
isIntType = false
|
|
}
|
|
for row := iterator.Begin(); row != iterator.End(); row = iterator.Next() {
|
|
if !selected[row.Idx()] {
|
|
continue
|
|
}
|
|
if isIntType {
|
|
filterResult, isNullResult, err = filter.EvalInt(ctx, row)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
selected[row.Idx()] = selected[row.Idx()] && !isNullResult && (filterResult != 0)
|
|
} else {
|
|
// TODO: should rewrite the filter to `cast(expr as SIGNED) != 0` and always use `EvalInt`.
|
|
bVal, isNullResult, err = EvalBool(ctx, []Expression{filter}, row)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
selected[row.Idx()] = selected[row.Idx()] && bVal
|
|
}
|
|
if isNull != nil {
|
|
isNull[row.Idx()] = isNull[row.Idx()] || isNullResult
|
|
}
|
|
}
|
|
}
|
|
return selected, isNull, nil
|
|
}
|
|
|
|
// vectorizedFilter filters by vector.
|
|
func vectorizedFilter(ctx sessionctx.Context, filters []Expression, iterator *chunk.Iterator4Chunk, selected []bool, isNull []bool) ([]bool, []bool, error) {
|
|
selected, isNull, err := VecEvalBool(ctx, filters, iterator.GetChunk(), selected, isNull)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return selected, isNull, nil
|
|
}
|
|
|