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.
3961 lines
108 KiB
3961 lines
108 KiB
// Copyright 2013 The ql Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSES/QL-LICENSE file.
|
|
|
|
// Copyright 2015 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 (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/pingcap/errors"
|
|
"github.com/pingcap/parser/ast"
|
|
"github.com/pingcap/parser/charset"
|
|
"github.com/pingcap/parser/mysql"
|
|
"github.com/pingcap/tidb/sessionctx"
|
|
"github.com/pingcap/tidb/sessionctx/variable"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util/chunk"
|
|
"github.com/pingcap/tidb/util/collate"
|
|
"github.com/pingcap/tidb/util/hack"
|
|
"github.com/pingcap/tidb/util/logutil"
|
|
"github.com/pingcap/tipb/go-tipb"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/text/transform"
|
|
)
|
|
|
|
var (
|
|
_ functionClass = &lengthFunctionClass{}
|
|
_ functionClass = &asciiFunctionClass{}
|
|
_ functionClass = &concatFunctionClass{}
|
|
_ functionClass = &concatWSFunctionClass{}
|
|
_ functionClass = &leftFunctionClass{}
|
|
_ functionClass = &repeatFunctionClass{}
|
|
_ functionClass = &lowerFunctionClass{}
|
|
_ functionClass = &reverseFunctionClass{}
|
|
_ functionClass = &spaceFunctionClass{}
|
|
_ functionClass = &upperFunctionClass{}
|
|
_ functionClass = &strcmpFunctionClass{}
|
|
_ functionClass = &replaceFunctionClass{}
|
|
_ functionClass = &convertFunctionClass{}
|
|
_ functionClass = &substringFunctionClass{}
|
|
_ functionClass = &substringIndexFunctionClass{}
|
|
_ functionClass = &locateFunctionClass{}
|
|
_ functionClass = &hexFunctionClass{}
|
|
_ functionClass = &unhexFunctionClass{}
|
|
_ functionClass = &trimFunctionClass{}
|
|
_ functionClass = &lTrimFunctionClass{}
|
|
_ functionClass = &rTrimFunctionClass{}
|
|
_ functionClass = &lpadFunctionClass{}
|
|
_ functionClass = &rpadFunctionClass{}
|
|
_ functionClass = &bitLengthFunctionClass{}
|
|
_ functionClass = &charFunctionClass{}
|
|
_ functionClass = &charLengthFunctionClass{}
|
|
_ functionClass = &findInSetFunctionClass{}
|
|
_ functionClass = &fieldFunctionClass{}
|
|
_ functionClass = &makeSetFunctionClass{}
|
|
_ functionClass = &octFunctionClass{}
|
|
_ functionClass = &ordFunctionClass{}
|
|
_ functionClass = "eFunctionClass{}
|
|
_ functionClass = &binFunctionClass{}
|
|
_ functionClass = &eltFunctionClass{}
|
|
_ functionClass = &exportSetFunctionClass{}
|
|
_ functionClass = &formatFunctionClass{}
|
|
_ functionClass = &fromBase64FunctionClass{}
|
|
_ functionClass = &toBase64FunctionClass{}
|
|
_ functionClass = &insertFunctionClass{}
|
|
_ functionClass = &instrFunctionClass{}
|
|
_ functionClass = &loadFileFunctionClass{}
|
|
_ functionClass = &weightStringFunctionClass{}
|
|
)
|
|
|
|
var (
|
|
_ builtinFunc = &builtinLengthSig{}
|
|
_ builtinFunc = &builtinASCIISig{}
|
|
_ builtinFunc = &builtinConcatSig{}
|
|
_ builtinFunc = &builtinConcatWSSig{}
|
|
_ builtinFunc = &builtinLeftSig{}
|
|
_ builtinFunc = &builtinLeftUTF8Sig{}
|
|
_ builtinFunc = &builtinRightSig{}
|
|
_ builtinFunc = &builtinRightUTF8Sig{}
|
|
_ builtinFunc = &builtinRepeatSig{}
|
|
_ builtinFunc = &builtinLowerSig{}
|
|
_ builtinFunc = &builtinReverseUTF8Sig{}
|
|
_ builtinFunc = &builtinReverseSig{}
|
|
_ builtinFunc = &builtinSpaceSig{}
|
|
_ builtinFunc = &builtinUpperUTF8Sig{}
|
|
_ builtinFunc = &builtinUpperSig{}
|
|
_ builtinFunc = &builtinStrcmpSig{}
|
|
_ builtinFunc = &builtinReplaceSig{}
|
|
_ builtinFunc = &builtinConvertSig{}
|
|
_ builtinFunc = &builtinSubstring2ArgsSig{}
|
|
_ builtinFunc = &builtinSubstring3ArgsSig{}
|
|
_ builtinFunc = &builtinSubstring2ArgsUTF8Sig{}
|
|
_ builtinFunc = &builtinSubstring3ArgsUTF8Sig{}
|
|
_ builtinFunc = &builtinSubstringIndexSig{}
|
|
_ builtinFunc = &builtinLocate2ArgsUTF8Sig{}
|
|
_ builtinFunc = &builtinLocate3ArgsUTF8Sig{}
|
|
_ builtinFunc = &builtinLocate2ArgsSig{}
|
|
_ builtinFunc = &builtinLocate3ArgsSig{}
|
|
_ builtinFunc = &builtinHexStrArgSig{}
|
|
_ builtinFunc = &builtinHexIntArgSig{}
|
|
_ builtinFunc = &builtinUnHexSig{}
|
|
_ builtinFunc = &builtinTrim1ArgSig{}
|
|
_ builtinFunc = &builtinTrim2ArgsSig{}
|
|
_ builtinFunc = &builtinTrim3ArgsSig{}
|
|
_ builtinFunc = &builtinLTrimSig{}
|
|
_ builtinFunc = &builtinRTrimSig{}
|
|
_ builtinFunc = &builtinLpadUTF8Sig{}
|
|
_ builtinFunc = &builtinLpadSig{}
|
|
_ builtinFunc = &builtinRpadUTF8Sig{}
|
|
_ builtinFunc = &builtinRpadSig{}
|
|
_ builtinFunc = &builtinBitLengthSig{}
|
|
_ builtinFunc = &builtinCharSig{}
|
|
_ builtinFunc = &builtinCharLengthUTF8Sig{}
|
|
_ builtinFunc = &builtinFindInSetSig{}
|
|
_ builtinFunc = &builtinMakeSetSig{}
|
|
_ builtinFunc = &builtinOctIntSig{}
|
|
_ builtinFunc = &builtinOctStringSig{}
|
|
_ builtinFunc = &builtinOrdSig{}
|
|
_ builtinFunc = &builtinQuoteSig{}
|
|
_ builtinFunc = &builtinBinSig{}
|
|
_ builtinFunc = &builtinEltSig{}
|
|
_ builtinFunc = &builtinExportSet3ArgSig{}
|
|
_ builtinFunc = &builtinExportSet4ArgSig{}
|
|
_ builtinFunc = &builtinExportSet5ArgSig{}
|
|
_ builtinFunc = &builtinFormatWithLocaleSig{}
|
|
_ builtinFunc = &builtinFormatSig{}
|
|
_ builtinFunc = &builtinFromBase64Sig{}
|
|
_ builtinFunc = &builtinToBase64Sig{}
|
|
_ builtinFunc = &builtinInsertSig{}
|
|
_ builtinFunc = &builtinInsertUTF8Sig{}
|
|
_ builtinFunc = &builtinInstrUTF8Sig{}
|
|
_ builtinFunc = &builtinInstrSig{}
|
|
_ builtinFunc = &builtinFieldRealSig{}
|
|
_ builtinFunc = &builtinFieldIntSig{}
|
|
_ builtinFunc = &builtinFieldStringSig{}
|
|
_ builtinFunc = &builtinWeightStringSig{}
|
|
)
|
|
|
|
func reverseBytes(origin []byte) []byte {
|
|
for i, length := 0, len(origin); i < length/2; i++ {
|
|
origin[i], origin[length-i-1] = origin[length-i-1], origin[i]
|
|
}
|
|
return origin
|
|
}
|
|
|
|
func reverseRunes(origin []rune) []rune {
|
|
for i, length := 0, len(origin); i < length/2; i++ {
|
|
origin[i], origin[length-i-1] = origin[length-i-1], origin[i]
|
|
}
|
|
return origin
|
|
}
|
|
|
|
// SetBinFlagOrBinStr sets resTp to binary string if argTp is a binary string,
|
|
// if not, sets the binary flag of resTp to true if argTp has binary flag.
|
|
func SetBinFlagOrBinStr(argTp *types.FieldType, resTp *types.FieldType) {
|
|
if types.IsBinaryStr(argTp) {
|
|
types.SetBinChsClnFlag(resTp)
|
|
} else if mysql.HasBinaryFlag(argTp.Flag) || !types.IsNonBinaryStr(argTp) {
|
|
resTp.Flag |= mysql.BinaryFlag
|
|
}
|
|
}
|
|
|
|
type lengthFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *lengthFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = 10
|
|
sig := &builtinLengthSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Length)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinLengthSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinLengthSig) Clone() builtinFunc {
|
|
newSig := &builtinLengthSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evaluates a builtinLengthSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html
|
|
func (b *builtinLengthSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
val, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
return int64(len([]byte(val))), false, nil
|
|
}
|
|
|
|
type asciiFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *asciiFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = 3
|
|
sig := &builtinASCIISig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_ASCII)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinASCIISig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinASCIISig) Clone() builtinFunc {
|
|
newSig := &builtinASCIISig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals a builtinASCIISig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_ascii
|
|
func (b *builtinASCIISig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
val, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
if len(val) == 0 {
|
|
return 0, false, nil
|
|
}
|
|
return int64(val[0]), false, nil
|
|
}
|
|
|
|
type concatFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *concatFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
argTps := make([]types.EvalType, 0, len(args))
|
|
for i := 0; i < len(args); i++ {
|
|
argTps = append(argTps, types.ETString)
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = 0
|
|
for i := range args {
|
|
argType := args[i].GetType()
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
|
|
if argType.Flen < 0 {
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
logutil.BgLogger().Warn("unexpected `Flen` value(-1) in CONCAT's args", zap.Int("arg's index", i))
|
|
}
|
|
bf.tp.Flen += argType.Flen
|
|
}
|
|
if bf.tp.Flen >= mysql.MaxBlobWidth {
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
}
|
|
|
|
valStr, _ := ctx.GetSessionVars().GetSystemVar(variable.MaxAllowedPacket)
|
|
maxAllowedPacket, err := strconv.ParseUint(valStr, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
|
|
sig := &builtinConcatSig{bf, maxAllowedPacket}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Concat)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinConcatSig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinConcatSig) Clone() builtinFunc {
|
|
newSig := &builtinConcatSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinConcatSig
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat
|
|
func (b *builtinConcatSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
var s []byte
|
|
for _, a := range b.getArgs() {
|
|
d, isNull, err = a.EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
if uint64(len(s)+len(d)) > b.maxAllowedPacket {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("concat", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
s = append(s, []byte(d)...)
|
|
}
|
|
return string(s), false, nil
|
|
}
|
|
|
|
type concatWSFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *concatWSFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
argTps := make([]types.EvalType, 0, len(args))
|
|
for i := 0; i < len(args); i++ {
|
|
argTps = append(argTps, types.ETString)
|
|
}
|
|
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = 0
|
|
|
|
for i := range args {
|
|
argType := args[i].GetType()
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
|
|
// skip separator param
|
|
if i != 0 {
|
|
if argType.Flen < 0 {
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
logutil.BgLogger().Warn("unexpected `Flen` value(-1) in CONCAT_WS's args", zap.Int("arg's index", i))
|
|
}
|
|
bf.tp.Flen += argType.Flen
|
|
}
|
|
}
|
|
|
|
// add separator
|
|
argsLen := len(args) - 1
|
|
bf.tp.Flen += argsLen - 1
|
|
|
|
if bf.tp.Flen >= mysql.MaxBlobWidth {
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
}
|
|
|
|
valStr, _ := ctx.GetSessionVars().GetSystemVar(variable.MaxAllowedPacket)
|
|
maxAllowedPacket, err := strconv.ParseUint(valStr, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
|
|
sig := &builtinConcatWSSig{bf, maxAllowedPacket}
|
|
sig.setPbCode(tipb.ScalarFuncSig_ConcatWS)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinConcatWSSig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinConcatWSSig) Clone() builtinFunc {
|
|
newSig := &builtinConcatWSSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a CONCAT_WS(separator,str1,str2,...).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat-ws
|
|
func (b *builtinConcatWSSig) evalString(row chunk.Row) (string, bool, error) {
|
|
args := b.getArgs()
|
|
strs := make([]string, 0, len(args))
|
|
var sep string
|
|
var targetLength int
|
|
|
|
N := len(args)
|
|
if N > 0 {
|
|
val, isNull, err := args[0].EvalString(b.ctx, row)
|
|
if err != nil || isNull {
|
|
// If the separator is NULL, the result is NULL.
|
|
return val, isNull, err
|
|
}
|
|
sep = val
|
|
}
|
|
for i := 1; i < N; i++ {
|
|
val, isNull, err := args[i].EvalString(b.ctx, row)
|
|
if err != nil {
|
|
return val, isNull, err
|
|
}
|
|
if isNull {
|
|
// CONCAT_WS() does not skip empty strings. However,
|
|
// it does skip any NULL values after the separator argument.
|
|
continue
|
|
}
|
|
|
|
targetLength += len(val)
|
|
if i > 1 {
|
|
targetLength += len(sep)
|
|
}
|
|
if uint64(targetLength) > b.maxAllowedPacket {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("concat_ws", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
strs = append(strs, val)
|
|
}
|
|
|
|
str := strings.Join(strs, sep)
|
|
// todo check whether the length of result is larger than Flen
|
|
//if b.tp.Flen != types.UnspecifiedLength && len(str) > b.tp.Flen {
|
|
// return "", true, nil
|
|
//}
|
|
return str, false, nil
|
|
}
|
|
|
|
type leftFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *leftFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETInt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argType := args[0].GetType()
|
|
bf.tp.Flen = argType.Flen
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
if types.IsBinaryStr(argType) {
|
|
sig := &builtinLeftSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Left)
|
|
return sig, nil
|
|
}
|
|
sig := &builtinLeftUTF8Sig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_LeftUTF8)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinLeftSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinLeftSig) Clone() builtinFunc {
|
|
newSig := &builtinLeftSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals LEFT(str,len).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_left
|
|
func (b *builtinLeftSig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
left, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
leftLength := int(left)
|
|
if strLength := len(str); leftLength > strLength {
|
|
leftLength = strLength
|
|
} else if leftLength < 0 {
|
|
leftLength = 0
|
|
}
|
|
return str[:leftLength], false, nil
|
|
}
|
|
|
|
type builtinLeftUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinLeftUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinLeftUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals LEFT(str,len).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_left
|
|
func (b *builtinLeftUTF8Sig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
left, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
runes, leftLength := []rune(str), int(left)
|
|
if runeLength := len(runes); leftLength > runeLength {
|
|
leftLength = runeLength
|
|
} else if leftLength < 0 {
|
|
leftLength = 0
|
|
}
|
|
return string(runes[:leftLength]), false, nil
|
|
}
|
|
|
|
type rightFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *rightFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETInt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argType := args[0].GetType()
|
|
bf.tp.Flen = argType.Flen
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
if types.IsBinaryStr(argType) {
|
|
sig := &builtinRightSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Right)
|
|
return sig, nil
|
|
}
|
|
sig := &builtinRightUTF8Sig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_RightUTF8)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinRightSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinRightSig) Clone() builtinFunc {
|
|
newSig := &builtinRightSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals RIGHT(str,len).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_right
|
|
func (b *builtinRightSig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
right, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
strLength, rightLength := len(str), int(right)
|
|
if rightLength > strLength {
|
|
rightLength = strLength
|
|
} else if rightLength < 0 {
|
|
rightLength = 0
|
|
}
|
|
return str[strLength-rightLength:], false, nil
|
|
}
|
|
|
|
type builtinRightUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinRightUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinRightUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals RIGHT(str,len).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_right
|
|
func (b *builtinRightUTF8Sig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
right, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
runes := []rune(str)
|
|
strLength, rightLength := len(runes), int(right)
|
|
if rightLength > strLength {
|
|
rightLength = strLength
|
|
} else if rightLength < 0 {
|
|
rightLength = 0
|
|
}
|
|
return string(runes[strLength-rightLength:]), false, nil
|
|
}
|
|
|
|
type repeatFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *repeatFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETInt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
SetBinFlagOrBinStr(args[0].GetType(), bf.tp)
|
|
valStr, _ := ctx.GetSessionVars().GetSystemVar(variable.MaxAllowedPacket)
|
|
maxAllowedPacket, err := strconv.ParseUint(valStr, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
sig := &builtinRepeatSig{bf, maxAllowedPacket}
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinRepeatSig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinRepeatSig) Clone() builtinFunc {
|
|
newSig := &builtinRepeatSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinRepeatSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_repeat
|
|
func (b *builtinRepeatSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", isNull, err
|
|
}
|
|
byteLength := len(str)
|
|
|
|
num, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", isNull, err
|
|
}
|
|
if num < 1 {
|
|
return "", false, nil
|
|
}
|
|
if num > math.MaxInt32 {
|
|
num = math.MaxInt32
|
|
}
|
|
|
|
if uint64(byteLength)*uint64(num) > b.maxAllowedPacket {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("repeat", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
|
|
if int64(byteLength) > int64(b.tp.Flen)/num {
|
|
return "", true, nil
|
|
}
|
|
return strings.Repeat(str, int(num)), false, nil
|
|
}
|
|
|
|
type lowerFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *lowerFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argTp := args[0].GetType()
|
|
bf.tp.Flen = argTp.Flen
|
|
SetBinFlagOrBinStr(argTp, bf.tp)
|
|
sig := &builtinLowerSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Lower)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinLowerSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinLowerSig) Clone() builtinFunc {
|
|
newSig := &builtinLowerSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinLowerSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_lower
|
|
func (b *builtinLowerSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
d, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
|
|
if types.IsBinaryStr(b.args[0].GetType()) {
|
|
return d, false, nil
|
|
}
|
|
|
|
return strings.ToLower(d), false, nil
|
|
}
|
|
|
|
type reverseFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *reverseFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
retTp := *args[0].GetType()
|
|
retTp.Tp = mysql.TypeVarString
|
|
retTp.Decimal = types.UnspecifiedLength
|
|
bf.tp = &retTp
|
|
var sig builtinFunc
|
|
if types.IsBinaryStr(bf.tp) {
|
|
sig = &builtinReverseSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Reverse)
|
|
} else {
|
|
sig = &builtinReverseUTF8Sig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_ReverseUTF8)
|
|
}
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinReverseSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinReverseSig) Clone() builtinFunc {
|
|
newSig := &builtinReverseSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a REVERSE(str).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_reverse
|
|
func (b *builtinReverseSig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
reversed := reverseBytes([]byte(str))
|
|
return string(reversed), false, nil
|
|
}
|
|
|
|
type builtinReverseUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinReverseUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinReverseUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a REVERSE(str).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_reverse
|
|
func (b *builtinReverseUTF8Sig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
reversed := reverseRunes([]rune(str))
|
|
return string(reversed), false, nil
|
|
}
|
|
|
|
type spaceFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *spaceFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETInt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Charset, bf.tp.Collate = ctx.GetSessionVars().GetCharsetInfo()
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
valStr, _ := ctx.GetSessionVars().GetSystemVar(variable.MaxAllowedPacket)
|
|
maxAllowedPacket, err := strconv.ParseUint(valStr, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
sig := &builtinSpaceSig{bf, maxAllowedPacket}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Space)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinSpaceSig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinSpaceSig) Clone() builtinFunc {
|
|
newSig := &builtinSpaceSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinSpaceSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_space
|
|
func (b *builtinSpaceSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
var x int64
|
|
|
|
x, isNull, err = b.args[0].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
if x < 0 {
|
|
x = 0
|
|
}
|
|
if uint64(x) > b.maxAllowedPacket {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("space", b.maxAllowedPacket))
|
|
return d, true, nil
|
|
}
|
|
if x > mysql.MaxBlobWidth {
|
|
return d, true, nil
|
|
}
|
|
return strings.Repeat(" ", int(x)), false, nil
|
|
}
|
|
|
|
type upperFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *upperFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argTp := args[0].GetType()
|
|
bf.tp.Flen = argTp.Flen
|
|
SetBinFlagOrBinStr(argTp, bf.tp)
|
|
var sig builtinFunc
|
|
if types.IsBinaryStr(argTp) {
|
|
sig = &builtinUpperSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Upper)
|
|
} else {
|
|
sig = &builtinUpperUTF8Sig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_UpperUTF8)
|
|
}
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinUpperUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinUpperUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinUpperUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinUpperUTF8Sig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_upper
|
|
func (b *builtinUpperUTF8Sig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
d, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
|
|
return strings.ToUpper(d), false, nil
|
|
}
|
|
|
|
type builtinUpperSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinUpperSig) Clone() builtinFunc {
|
|
newSig := &builtinUpperSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinUpperSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_upper
|
|
func (b *builtinUpperSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
d, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
|
|
return d, false, nil
|
|
}
|
|
|
|
type strcmpFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *strcmpFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = 2
|
|
types.SetBinChsClnFlag(bf.tp)
|
|
sig := &builtinStrcmpSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Strcmp)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinStrcmpSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinStrcmpSig) Clone() builtinFunc {
|
|
newSig := &builtinStrcmpSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals a builtinStrcmpSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-comparison-functions.html
|
|
func (b *builtinStrcmpSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
var (
|
|
left, right string
|
|
isNull bool
|
|
err error
|
|
)
|
|
|
|
left, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
right, isNull, err = b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
res := types.CompareString(left, right, b.collation)
|
|
return int64(res), false, nil
|
|
}
|
|
|
|
type replaceFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *replaceFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = c.fixLength(args)
|
|
for _, a := range args {
|
|
SetBinFlagOrBinStr(a.GetType(), bf.tp)
|
|
}
|
|
sig := &builtinReplaceSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Replace)
|
|
return sig, nil
|
|
}
|
|
|
|
// fixLength calculate the Flen of the return type.
|
|
func (c *replaceFunctionClass) fixLength(args []Expression) int {
|
|
charLen := args[0].GetType().Flen
|
|
oldStrLen := args[1].GetType().Flen
|
|
diff := args[2].GetType().Flen - oldStrLen
|
|
if diff > 0 && oldStrLen > 0 {
|
|
charLen += (charLen / oldStrLen) * diff
|
|
}
|
|
return charLen
|
|
}
|
|
|
|
type builtinReplaceSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinReplaceSig) Clone() builtinFunc {
|
|
newSig := &builtinReplaceSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinReplaceSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_replace
|
|
func (b *builtinReplaceSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
var str, oldStr, newStr string
|
|
|
|
str, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
oldStr, isNull, err = b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
newStr, isNull, err = b.args[2].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
if oldStr == "" {
|
|
return str, false, nil
|
|
}
|
|
return strings.Replace(str, oldStr, newStr, -1), false, nil
|
|
}
|
|
|
|
type convertFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *convertFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
charsetArg, ok := args[1].(*Constant)
|
|
if !ok {
|
|
// `args[1]` is limited by parser to be a constant string,
|
|
// should never go into here.
|
|
return nil, errIncorrectArgs.GenWithStackByArgs("charset")
|
|
}
|
|
transcodingName := charsetArg.Value.GetString()
|
|
bf.tp.Charset = strings.ToLower(transcodingName)
|
|
// Quoted about the behavior of syntax CONVERT(expr, type) to CHAR():
|
|
// In all cases, the string has the default collation for the character set.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert
|
|
// Here in syntax CONVERT(expr USING transcoding_name), behavior is kept the same,
|
|
// picking the default collation of target charset.
|
|
bf.tp.Collate, err = charset.GetDefaultCollation(bf.tp.Charset)
|
|
if err != nil {
|
|
return nil, errUnknownCharacterSet.GenWithStackByArgs(transcodingName)
|
|
}
|
|
// Result will be a binary string if converts charset to BINARY.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/charset-binary-set.html
|
|
if types.IsBinaryStr(bf.tp) {
|
|
types.SetBinChsClnFlag(bf.tp)
|
|
} else {
|
|
bf.tp.Flag &= ^mysql.BinaryFlag
|
|
}
|
|
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
sig := &builtinConvertSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Convert)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinConvertSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinConvertSig) Clone() builtinFunc {
|
|
newSig := &builtinConvertSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals CONVERT(expr USING transcoding_name).
|
|
// Syntax CONVERT(expr, type) is parsed as cast expr so not handled here.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert
|
|
func (b *builtinConvertSig) evalString(row chunk.Row) (string, bool, error) {
|
|
expr, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
// Since charset is already validated and set from getFunction(), there's no
|
|
// need to get charset from args again.
|
|
encoding, _ := charset.Lookup(b.tp.Charset)
|
|
// However, if `b.tp.Charset` is abnormally set to a wrong charset, we still
|
|
// return with error.
|
|
if encoding == nil {
|
|
return "", true, errUnknownCharacterSet.GenWithStackByArgs(b.tp.Charset)
|
|
}
|
|
|
|
target, _, err := transform.String(encoding.NewDecoder(), expr)
|
|
return target, err != nil, err
|
|
}
|
|
|
|
type substringFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *substringFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
argTps := []types.EvalType{types.ETString, types.ETInt}
|
|
if len(args) == 3 {
|
|
argTps = append(argTps, types.ETInt)
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
argType := args[0].GetType()
|
|
bf.tp.Flen = argType.Flen
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
|
|
var sig builtinFunc
|
|
switch {
|
|
case len(args) == 3 && types.IsBinaryStr(argType):
|
|
sig = &builtinSubstring3ArgsSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Substring3Args)
|
|
case len(args) == 3:
|
|
sig = &builtinSubstring3ArgsUTF8Sig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Substring3ArgsUTF8)
|
|
case len(args) == 2 && types.IsBinaryStr(argType):
|
|
sig = &builtinSubstring2ArgsSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Substring2Args)
|
|
case len(args) == 2:
|
|
sig = &builtinSubstring2ArgsUTF8Sig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Substring2ArgsUTF8)
|
|
default:
|
|
// Should never happens.
|
|
return nil, errors.Errorf("SUBSTR invalid arg length, expect 2 or 3 but got: %v", len(args))
|
|
}
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinSubstring2ArgsSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinSubstring2ArgsSig) Clone() builtinFunc {
|
|
newSig := &builtinSubstring2ArgsSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals SUBSTR(str,pos), SUBSTR(str FROM pos), SUBSTR() is a synonym for SUBSTRING().
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_substr
|
|
func (b *builtinSubstring2ArgsSig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
pos, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
length := int64(len(str))
|
|
if pos < 0 {
|
|
pos += length
|
|
} else {
|
|
pos--
|
|
}
|
|
if pos > length || pos < 0 {
|
|
pos = length
|
|
}
|
|
return str[pos:], false, nil
|
|
}
|
|
|
|
type builtinSubstring2ArgsUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinSubstring2ArgsUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinSubstring2ArgsUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals SUBSTR(str,pos), SUBSTR(str FROM pos), SUBSTR() is a synonym for SUBSTRING().
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_substr
|
|
func (b *builtinSubstring2ArgsUTF8Sig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
pos, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
runes := []rune(str)
|
|
length := int64(len(runes))
|
|
if pos < 0 {
|
|
pos += length
|
|
} else {
|
|
pos--
|
|
}
|
|
if pos > length || pos < 0 {
|
|
pos = length
|
|
}
|
|
return string(runes[pos:]), false, nil
|
|
}
|
|
|
|
type builtinSubstring3ArgsSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinSubstring3ArgsSig) Clone() builtinFunc {
|
|
newSig := &builtinSubstring3ArgsSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals SUBSTR(str,pos,len), SUBSTR(str FROM pos FOR len), SUBSTR() is a synonym for SUBSTRING().
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_substr
|
|
func (b *builtinSubstring3ArgsSig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
pos, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
length, isNull, err := b.args[2].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
byteLen := int64(len(str))
|
|
if pos < 0 {
|
|
pos += byteLen
|
|
} else {
|
|
pos--
|
|
}
|
|
if pos > byteLen || pos < 0 {
|
|
pos = byteLen
|
|
}
|
|
end := pos + length
|
|
if end < pos {
|
|
return "", false, nil
|
|
} else if end < byteLen {
|
|
return str[pos:end], false, nil
|
|
}
|
|
return str[pos:], false, nil
|
|
}
|
|
|
|
type builtinSubstring3ArgsUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinSubstring3ArgsUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinSubstring3ArgsUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals SUBSTR(str,pos,len), SUBSTR(str FROM pos FOR len), SUBSTR() is a synonym for SUBSTRING().
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_substr
|
|
func (b *builtinSubstring3ArgsUTF8Sig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
pos, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
length, isNull, err := b.args[2].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
runes := []rune(str)
|
|
numRunes := int64(len(runes))
|
|
if pos < 0 {
|
|
pos += numRunes
|
|
} else {
|
|
pos--
|
|
}
|
|
if pos > numRunes || pos < 0 {
|
|
pos = numRunes
|
|
}
|
|
end := pos + length
|
|
if end < pos {
|
|
return "", false, nil
|
|
} else if end < numRunes {
|
|
return string(runes[pos:end]), false, nil
|
|
}
|
|
return string(runes[pos:]), false, nil
|
|
}
|
|
|
|
type substringIndexFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *substringIndexFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETString, types.ETInt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argType := args[0].GetType()
|
|
bf.tp.Flen = argType.Flen
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
sig := &builtinSubstringIndexSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_SubstringIndex)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinSubstringIndexSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinSubstringIndexSig) Clone() builtinFunc {
|
|
newSig := &builtinSubstringIndexSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinSubstringIndexSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_substring-index
|
|
func (b *builtinSubstringIndexSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
var (
|
|
str, delim string
|
|
count int64
|
|
)
|
|
str, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
delim, isNull, err = b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
count, isNull, err = b.args[2].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
if len(delim) == 0 {
|
|
return "", false, nil
|
|
}
|
|
|
|
strs := strings.Split(str, delim)
|
|
start, end := int64(0), int64(len(strs))
|
|
if count > 0 {
|
|
// If count is positive, everything to the left of the final delimiter (counting from the left) is returned.
|
|
if count < end {
|
|
end = count
|
|
}
|
|
} else {
|
|
// If count is negative, everything to the right of the final delimiter (counting from the right) is returned.
|
|
count = -count
|
|
if count < 0 {
|
|
// -count overflows max int64, returns an empty string.
|
|
return "", false, nil
|
|
}
|
|
|
|
if count < end {
|
|
start = end - count
|
|
}
|
|
}
|
|
substrs := strs[start:end]
|
|
return strings.Join(substrs, delim), false, nil
|
|
}
|
|
|
|
type locateFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *locateFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
hasStartPos, argTps := len(args) == 3, []types.EvalType{types.ETString, types.ETString}
|
|
if hasStartPos {
|
|
argTps = append(argTps, types.ETInt)
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var sig builtinFunc
|
|
// Locate is multibyte safe.
|
|
useBinary := bf.collation == charset.CollationBin
|
|
switch {
|
|
case hasStartPos && useBinary:
|
|
sig = &builtinLocate3ArgsSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Locate3Args)
|
|
case hasStartPos:
|
|
sig = &builtinLocate3ArgsUTF8Sig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Locate3ArgsUTF8)
|
|
case useBinary:
|
|
sig = &builtinLocate2ArgsSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Locate2Args)
|
|
default:
|
|
sig = &builtinLocate2ArgsUTF8Sig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Locate2ArgsUTF8)
|
|
}
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinLocate2ArgsSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinLocate2ArgsSig) Clone() builtinFunc {
|
|
newSig := &builtinLocate2ArgsSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals LOCATE(substr,str), case-sensitive.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_locate
|
|
func (b *builtinLocate2ArgsSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
subStr, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
str, isNull, err := b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
subStrLen := len(subStr)
|
|
if subStrLen == 0 {
|
|
return 1, false, nil
|
|
}
|
|
ret, idx := 0, strings.Index(str, subStr)
|
|
if idx != -1 {
|
|
ret = idx + 1
|
|
}
|
|
return int64(ret), false, nil
|
|
}
|
|
|
|
type builtinLocate2ArgsUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinLocate2ArgsUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinLocate2ArgsUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals LOCATE(substr,str).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_locate
|
|
func (b *builtinLocate2ArgsUTF8Sig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
subStr, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
str, isNull, err := b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
if int64(len([]rune(subStr))) == 0 {
|
|
return 1, false, nil
|
|
}
|
|
if collate.IsCICollation(b.collation) {
|
|
str = strings.ToLower(str)
|
|
subStr = strings.ToLower(subStr)
|
|
}
|
|
ret, idx := 0, strings.Index(str, subStr)
|
|
if idx != -1 {
|
|
ret = utf8.RuneCountInString(str[:idx]) + 1
|
|
}
|
|
return int64(ret), false, nil
|
|
}
|
|
|
|
type builtinLocate3ArgsSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinLocate3ArgsSig) Clone() builtinFunc {
|
|
newSig := &builtinLocate3ArgsSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals LOCATE(substr,str,pos), case-sensitive.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_locate
|
|
func (b *builtinLocate3ArgsSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
subStr, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
str, isNull, err := b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
pos, isNull, err := b.args[2].EvalInt(b.ctx, row)
|
|
// Transfer the argument which starts from 1 to real index which starts from 0.
|
|
pos--
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
subStrLen := len(subStr)
|
|
if pos < 0 || pos > int64(len(str)-subStrLen) {
|
|
return 0, false, nil
|
|
} else if subStrLen == 0 {
|
|
return pos + 1, false, nil
|
|
}
|
|
slice := str[pos:]
|
|
idx := strings.Index(slice, subStr)
|
|
if idx != -1 {
|
|
return pos + int64(idx) + 1, false, nil
|
|
}
|
|
return 0, false, nil
|
|
}
|
|
|
|
type builtinLocate3ArgsUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinLocate3ArgsUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinLocate3ArgsUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals LOCATE(substr,str,pos).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_locate
|
|
func (b *builtinLocate3ArgsUTF8Sig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
subStr, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
str, isNull, err := b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
if collate.IsCICollation(b.collation) {
|
|
subStr = strings.ToLower(subStr)
|
|
str = strings.ToLower(str)
|
|
}
|
|
pos, isNull, err := b.args[2].EvalInt(b.ctx, row)
|
|
// Transfer the argument which starts from 1 to real index which starts from 0.
|
|
pos--
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
subStrLen := len([]rune(subStr))
|
|
if pos < 0 || pos > int64(len([]rune(str))-subStrLen) {
|
|
return 0, false, nil
|
|
} else if subStrLen == 0 {
|
|
return pos + 1, false, nil
|
|
}
|
|
slice := string([]rune(str)[pos:])
|
|
idx := strings.Index(slice, subStr)
|
|
if idx != -1 {
|
|
return pos + int64(utf8.RuneCountInString(slice[:idx])) + 1, false, nil
|
|
}
|
|
return 0, false, nil
|
|
}
|
|
|
|
type hexFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *hexFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
argTp := args[0].GetType().EvalType()
|
|
switch argTp {
|
|
case types.ETString, types.ETDatetime, types.ETTimestamp, types.ETDuration, types.ETJson:
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Charset, bf.tp.Collate = ctx.GetSessionVars().GetCharsetInfo()
|
|
// Use UTF-8 as default
|
|
bf.tp.Flen = args[0].GetType().Flen * 3 * 2
|
|
sig := &builtinHexStrArgSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_HexStrArg)
|
|
return sig, nil
|
|
case types.ETInt, types.ETReal, types.ETDecimal:
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETInt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = args[0].GetType().Flen * 2
|
|
bf.tp.Charset, bf.tp.Collate = ctx.GetSessionVars().GetCharsetInfo()
|
|
sig := &builtinHexIntArgSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_HexIntArg)
|
|
return sig, nil
|
|
default:
|
|
return nil, errors.Errorf("Hex invalid args, need int or string but get %T", args[0].GetType())
|
|
}
|
|
}
|
|
|
|
type builtinHexStrArgSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinHexStrArgSig) Clone() builtinFunc {
|
|
newSig := &builtinHexStrArgSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinHexStrArgSig, corresponding to hex(str)
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_hex
|
|
func (b *builtinHexStrArgSig) evalString(row chunk.Row) (string, bool, error) {
|
|
d, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
return strings.ToUpper(hex.EncodeToString(hack.Slice(d))), false, nil
|
|
}
|
|
|
|
type builtinHexIntArgSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinHexIntArgSig) Clone() builtinFunc {
|
|
newSig := &builtinHexIntArgSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinHexIntArgSig, corresponding to hex(N)
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_hex
|
|
func (b *builtinHexIntArgSig) evalString(row chunk.Row) (string, bool, error) {
|
|
x, isNull, err := b.args[0].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", isNull, err
|
|
}
|
|
return strings.ToUpper(fmt.Sprintf("%x", uint64(x))), false, nil
|
|
}
|
|
|
|
type unhexFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *unhexFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
var retFlen int
|
|
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
argType := args[0].GetType()
|
|
argEvalTp := argType.EvalType()
|
|
switch argEvalTp {
|
|
case types.ETString, types.ETDatetime, types.ETTimestamp, types.ETDuration, types.ETJson:
|
|
// Use UTF-8 as default charset, so there're (Flen * 3 + 1) / 2 byte-pairs
|
|
retFlen = (argType.Flen*3 + 1) / 2
|
|
case types.ETInt, types.ETReal, types.ETDecimal:
|
|
// For number value, there're (Flen + 1) / 2 byte-pairs
|
|
retFlen = (argType.Flen + 1) / 2
|
|
default:
|
|
return nil, errors.Errorf("Unhex invalid args, need int or string but get %s", argType)
|
|
}
|
|
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = retFlen
|
|
types.SetBinChsClnFlag(bf.tp)
|
|
sig := &builtinUnHexSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_UnHex)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinUnHexSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinUnHexSig) Clone() builtinFunc {
|
|
newSig := &builtinUnHexSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinUnHexSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_unhex
|
|
func (b *builtinUnHexSig) evalString(row chunk.Row) (string, bool, error) {
|
|
var bs []byte
|
|
|
|
d, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
// Add a '0' to the front, if the length is not the multiple of 2
|
|
if len(d)%2 != 0 {
|
|
d = "0" + d
|
|
}
|
|
bs, err = hex.DecodeString(d)
|
|
if err != nil {
|
|
return "", true, nil
|
|
}
|
|
return string(bs), false, nil
|
|
}
|
|
|
|
const spaceChars = " "
|
|
|
|
type trimFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
// getFunction sets trim built-in function signature.
|
|
// The syntax of trim in mysql is 'TRIM([{BOTH | LEADING | TRAILING} [remstr] FROM] str), TRIM([remstr FROM] str)',
|
|
// but we wil convert it into trim(str), trim(str, remstr) and trim(str, remstr, direction) in AST.
|
|
func (c *trimFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch len(args) {
|
|
case 1:
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argType := args[0].GetType()
|
|
bf.tp.Flen = argType.Flen
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
sig := &builtinTrim1ArgSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Trim1Arg)
|
|
return sig, nil
|
|
|
|
case 2:
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argType := args[0].GetType()
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
sig := &builtinTrim2ArgsSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Trim2Args)
|
|
return sig, nil
|
|
|
|
case 3:
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETString, types.ETInt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argType := args[0].GetType()
|
|
bf.tp.Flen = argType.Flen
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
sig := &builtinTrim3ArgsSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Trim3Args)
|
|
return sig, nil
|
|
|
|
default:
|
|
return nil, c.verifyArgs(args)
|
|
}
|
|
}
|
|
|
|
type builtinTrim1ArgSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinTrim1ArgSig) Clone() builtinFunc {
|
|
newSig := &builtinTrim1ArgSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinTrim1ArgSig, corresponding to trim(str)
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_trim
|
|
func (b *builtinTrim1ArgSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
d, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
return strings.Trim(d, spaceChars), false, nil
|
|
}
|
|
|
|
type builtinTrim2ArgsSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinTrim2ArgsSig) Clone() builtinFunc {
|
|
newSig := &builtinTrim2ArgsSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinTrim2ArgsSig, corresponding to trim(str, remstr)
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_trim
|
|
func (b *builtinTrim2ArgsSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
var str, remstr string
|
|
|
|
str, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
remstr, isNull, err = b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
d = trimLeft(str, remstr)
|
|
d = trimRight(d, remstr)
|
|
return d, false, nil
|
|
}
|
|
|
|
type builtinTrim3ArgsSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinTrim3ArgsSig) Clone() builtinFunc {
|
|
newSig := &builtinTrim3ArgsSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinTrim3ArgsSig, corresponding to trim(str, remstr, direction)
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_trim
|
|
func (b *builtinTrim3ArgsSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
var (
|
|
str, remstr string
|
|
x int64
|
|
direction ast.TrimDirectionType
|
|
isRemStrNull bool
|
|
)
|
|
str, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
remstr, isRemStrNull, err = b.args[1].EvalString(b.ctx, row)
|
|
if err != nil {
|
|
return d, isNull, err
|
|
}
|
|
x, isNull, err = b.args[2].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
direction = ast.TrimDirectionType(x)
|
|
if direction == ast.TrimLeading {
|
|
if isRemStrNull {
|
|
d = strings.TrimLeft(str, spaceChars)
|
|
} else {
|
|
d = trimLeft(str, remstr)
|
|
}
|
|
} else if direction == ast.TrimTrailing {
|
|
if isRemStrNull {
|
|
d = strings.TrimRight(str, spaceChars)
|
|
} else {
|
|
d = trimRight(str, remstr)
|
|
}
|
|
} else {
|
|
if isRemStrNull {
|
|
d = strings.Trim(str, spaceChars)
|
|
} else {
|
|
d = trimLeft(str, remstr)
|
|
d = trimRight(d, remstr)
|
|
}
|
|
}
|
|
return d, false, nil
|
|
}
|
|
|
|
type lTrimFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *lTrimFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argType := args[0].GetType()
|
|
bf.tp.Flen = argType.Flen
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
sig := &builtinLTrimSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_LTrim)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinLTrimSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinLTrimSig) Clone() builtinFunc {
|
|
newSig := &builtinLTrimSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinLTrimSig
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_ltrim
|
|
func (b *builtinLTrimSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
d, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
return strings.TrimLeft(d, spaceChars), false, nil
|
|
}
|
|
|
|
type rTrimFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *rTrimFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argType := args[0].GetType()
|
|
bf.tp.Flen = argType.Flen
|
|
SetBinFlagOrBinStr(argType, bf.tp)
|
|
sig := &builtinRTrimSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_RTrim)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinRTrimSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinRTrimSig) Clone() builtinFunc {
|
|
newSig := &builtinRTrimSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a builtinRTrimSig
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_rtrim
|
|
func (b *builtinRTrimSig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
d, isNull, err = b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return d, isNull, err
|
|
}
|
|
return strings.TrimRight(d, spaceChars), false, nil
|
|
}
|
|
|
|
func trimLeft(str, remstr string) string {
|
|
for {
|
|
x := strings.TrimPrefix(str, remstr)
|
|
if len(x) == len(str) {
|
|
return x
|
|
}
|
|
str = x
|
|
}
|
|
}
|
|
|
|
func trimRight(str, remstr string) string {
|
|
for {
|
|
x := strings.TrimSuffix(str, remstr)
|
|
if len(x) == len(str) {
|
|
return x
|
|
}
|
|
str = x
|
|
}
|
|
}
|
|
|
|
func getFlen4LpadAndRpad(ctx sessionctx.Context, arg Expression) int {
|
|
if constant, ok := arg.(*Constant); ok {
|
|
length, isNull, err := constant.EvalInt(ctx, chunk.Row{})
|
|
if err != nil {
|
|
logutil.BgLogger().Error("eval `Flen` for LPAD/RPAD", zap.Error(err))
|
|
}
|
|
if isNull || err != nil || length > mysql.MaxBlobWidth {
|
|
return mysql.MaxBlobWidth
|
|
}
|
|
return int(length)
|
|
}
|
|
return mysql.MaxBlobWidth
|
|
}
|
|
|
|
type lpadFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *lpadFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETInt, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = getFlen4LpadAndRpad(bf.ctx, args[1])
|
|
SetBinFlagOrBinStr(args[0].GetType(), bf.tp)
|
|
SetBinFlagOrBinStr(args[2].GetType(), bf.tp)
|
|
|
|
valStr, _ := ctx.GetSessionVars().GetSystemVar(variable.MaxAllowedPacket)
|
|
maxAllowedPacket, err := strconv.ParseUint(valStr, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
|
|
if types.IsBinaryStr(args[0].GetType()) || types.IsBinaryStr(args[2].GetType()) {
|
|
sig := &builtinLpadSig{bf, maxAllowedPacket}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Lpad)
|
|
return sig, nil
|
|
}
|
|
if bf.tp.Flen *= 4; bf.tp.Flen > mysql.MaxBlobWidth {
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
}
|
|
sig := &builtinLpadUTF8Sig{bf, maxAllowedPacket}
|
|
sig.setPbCode(tipb.ScalarFuncSig_LpadUTF8)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinLpadSig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinLpadSig) Clone() builtinFunc {
|
|
newSig := &builtinLpadSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals LPAD(str,len,padstr).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_lpad
|
|
func (b *builtinLpadSig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
byteLength := len(str)
|
|
|
|
length, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
targetLength := int(length)
|
|
|
|
if uint64(targetLength) > b.maxAllowedPacket {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("lpad", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
|
|
padStr, isNull, err := b.args[2].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
padLength := len(padStr)
|
|
|
|
if targetLength < 0 || targetLength > b.tp.Flen || (byteLength < targetLength && padLength == 0) {
|
|
return "", true, nil
|
|
}
|
|
|
|
if tailLen := targetLength - byteLength; tailLen > 0 {
|
|
repeatCount := tailLen/padLength + 1
|
|
str = strings.Repeat(padStr, repeatCount)[:tailLen] + str
|
|
}
|
|
return str[:targetLength], false, nil
|
|
}
|
|
|
|
type builtinLpadUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinLpadUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinLpadUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals LPAD(str,len,padstr).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_lpad
|
|
func (b *builtinLpadUTF8Sig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
runeLength := len([]rune(str))
|
|
|
|
length, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
targetLength := int(length)
|
|
|
|
if uint64(targetLength)*uint64(mysql.MaxBytesOfCharacter) > b.maxAllowedPacket {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("lpad", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
|
|
padStr, isNull, err := b.args[2].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
padLength := len([]rune(padStr))
|
|
|
|
if targetLength < 0 || targetLength*4 > b.tp.Flen || (runeLength < targetLength && padLength == 0) {
|
|
return "", true, nil
|
|
}
|
|
|
|
if tailLen := targetLength - runeLength; tailLen > 0 {
|
|
repeatCount := tailLen/padLength + 1
|
|
str = string([]rune(strings.Repeat(padStr, repeatCount))[:tailLen]) + str
|
|
}
|
|
return string([]rune(str)[:targetLength]), false, nil
|
|
}
|
|
|
|
type rpadFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *rpadFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETInt, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = getFlen4LpadAndRpad(bf.ctx, args[1])
|
|
SetBinFlagOrBinStr(args[0].GetType(), bf.tp)
|
|
SetBinFlagOrBinStr(args[2].GetType(), bf.tp)
|
|
|
|
valStr, _ := ctx.GetSessionVars().GetSystemVar(variable.MaxAllowedPacket)
|
|
maxAllowedPacket, err := strconv.ParseUint(valStr, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
|
|
if types.IsBinaryStr(args[0].GetType()) || types.IsBinaryStr(args[2].GetType()) {
|
|
sig := &builtinRpadSig{bf, maxAllowedPacket}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Rpad)
|
|
return sig, nil
|
|
}
|
|
if bf.tp.Flen *= 4; bf.tp.Flen > mysql.MaxBlobWidth {
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
}
|
|
sig := &builtinRpadUTF8Sig{bf, maxAllowedPacket}
|
|
sig.setPbCode(tipb.ScalarFuncSig_RpadUTF8)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinRpadSig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinRpadSig) Clone() builtinFunc {
|
|
newSig := &builtinRpadSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals RPAD(str,len,padstr).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_rpad
|
|
func (b *builtinRpadSig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
byteLength := len(str)
|
|
|
|
length, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
targetLength := int(length)
|
|
if uint64(targetLength) > b.maxAllowedPacket {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("rpad", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
|
|
padStr, isNull, err := b.args[2].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
padLength := len(padStr)
|
|
|
|
if targetLength < 0 || targetLength > b.tp.Flen || (byteLength < targetLength && padLength == 0) {
|
|
return "", true, nil
|
|
}
|
|
|
|
if tailLen := targetLength - byteLength; tailLen > 0 {
|
|
repeatCount := tailLen/padLength + 1
|
|
str = str + strings.Repeat(padStr, repeatCount)
|
|
}
|
|
return str[:targetLength], false, nil
|
|
}
|
|
|
|
type builtinRpadUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinRpadUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinRpadUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals RPAD(str,len,padstr).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_rpad
|
|
func (b *builtinRpadUTF8Sig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
runeLength := len([]rune(str))
|
|
|
|
length, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
targetLength := int(length)
|
|
|
|
if uint64(targetLength)*uint64(mysql.MaxBytesOfCharacter) > b.maxAllowedPacket {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("rpad", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
|
|
padStr, isNull, err := b.args[2].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
padLength := len([]rune(padStr))
|
|
|
|
if targetLength < 0 || targetLength*4 > b.tp.Flen || (runeLength < targetLength && padLength == 0) {
|
|
return "", true, nil
|
|
}
|
|
|
|
if tailLen := targetLength - runeLength; tailLen > 0 {
|
|
repeatCount := tailLen/padLength + 1
|
|
str = str + strings.Repeat(padStr, repeatCount)
|
|
}
|
|
return string([]rune(str)[:targetLength]), false, nil
|
|
}
|
|
|
|
type bitLengthFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *bitLengthFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = 10
|
|
sig := &builtinBitLengthSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_BitLength)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinBitLengthSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinBitLengthSig) Clone() builtinFunc {
|
|
newSig := &builtinBitLengthSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evaluates a builtinBitLengthSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_bit-length
|
|
func (b *builtinBitLengthSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
val, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
|
|
return int64(len(val) * 8), false, nil
|
|
}
|
|
|
|
type charFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *charFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
argTps := make([]types.EvalType, 0, len(args))
|
|
for i := 0; i < len(args)-1; i++ {
|
|
argTps = append(argTps, types.ETInt)
|
|
}
|
|
argTps = append(argTps, types.ETString)
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// The last argument represents the charset name after "using".
|
|
if _, ok := args[len(args)-1].(*Constant); !ok {
|
|
// If we got there, there must be something wrong in other places.
|
|
logutil.BgLogger().Warn(fmt.Sprintf("The last argument in char function must be constant, but got %T", args[len(args)-1]))
|
|
return nil, errIncorrectArgs
|
|
}
|
|
charsetName, isNull, err := args[len(args)-1].EvalString(ctx, chunk.Row{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isNull {
|
|
// Use the default charset binary if it is nil.
|
|
bf.tp.Charset, bf.tp.Collate = charset.CharsetBin, charset.CollationBin
|
|
bf.tp.Flag |= mysql.BinaryFlag
|
|
} else {
|
|
bf.tp.Charset = charsetName
|
|
defaultCollate, err := charset.GetDefaultCollation(charsetName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Collate = defaultCollate
|
|
}
|
|
bf.tp.Flen = 4 * (len(args) - 1)
|
|
|
|
sig := &builtinCharSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Char)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinCharSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinCharSig) Clone() builtinFunc {
|
|
newSig := &builtinCharSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
func (b *builtinCharSig) convertToBytes(ints []int64) []byte {
|
|
buffer := bytes.NewBuffer([]byte{})
|
|
for i := len(ints) - 1; i >= 0; i-- {
|
|
for count, val := 0, ints[i]; count < 4; count++ {
|
|
buffer.WriteByte(byte(val & 0xff))
|
|
if val >>= 8; val == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return reverseBytes(buffer.Bytes())
|
|
}
|
|
|
|
// evalString evals CHAR(N,... [USING charset_name]).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_char.
|
|
func (b *builtinCharSig) evalString(row chunk.Row) (string, bool, error) {
|
|
bigints := make([]int64, 0, len(b.args)-1)
|
|
|
|
for i := 0; i < len(b.args)-1; i++ {
|
|
val, IsNull, err := b.args[i].EvalInt(b.ctx, row)
|
|
if err != nil {
|
|
return "", true, err
|
|
}
|
|
if IsNull {
|
|
continue
|
|
}
|
|
bigints = append(bigints, val)
|
|
}
|
|
result := string(b.convertToBytes(bigints))
|
|
return result, false, nil
|
|
}
|
|
|
|
type charLengthFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *charLengthFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if argsErr := c.verifyArgs(args); argsErr != nil {
|
|
return nil, argsErr
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if types.IsBinaryStr(args[0].GetType()) {
|
|
sig := &builtinCharLengthBinarySig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_CharLength)
|
|
return sig, nil
|
|
}
|
|
sig := &builtinCharLengthUTF8Sig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_CharLengthUTF8)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinCharLengthBinarySig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinCharLengthBinarySig) Clone() builtinFunc {
|
|
newSig := &builtinCharLengthBinarySig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals a builtinCharLengthUTF8Sig for binary string type.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_char-length
|
|
func (b *builtinCharLengthBinarySig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
val, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
return int64(len(val)), false, nil
|
|
}
|
|
|
|
type builtinCharLengthUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinCharLengthUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinCharLengthUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals a builtinCharLengthUTF8Sig for non-binary string type.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_char-length
|
|
func (b *builtinCharLengthUTF8Sig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
val, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
return int64(len([]rune(val))), false, nil
|
|
}
|
|
|
|
type findInSetFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *findInSetFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = 3
|
|
sig := &builtinFindInSetSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_FindInSet)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinFindInSetSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinFindInSetSig) Clone() builtinFunc {
|
|
newSig := &builtinFindInSetSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals FIND_IN_SET(str,strlist).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_find-in-set
|
|
// TODO: This function can be optimized by using bit arithmetic when the first argument is
|
|
// a constant string and the second is a column of type SET.
|
|
func (b *builtinFindInSetSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
|
|
strlist, isNull, err := b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
|
|
if len(strlist) == 0 {
|
|
return 0, false, nil
|
|
}
|
|
|
|
for i, strInSet := range strings.Split(strlist, ",") {
|
|
if b.ctor.Compare(str, strInSet) == 0 {
|
|
return int64(i + 1), false, nil
|
|
}
|
|
}
|
|
return 0, false, nil
|
|
}
|
|
|
|
type fieldFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *fieldFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
isAllString, isAllNumber := true, true
|
|
for i, length := 0, len(args); i < length; i++ {
|
|
argTp := args[i].GetType().EvalType()
|
|
isAllString = isAllString && (argTp == types.ETString)
|
|
isAllNumber = isAllNumber && (argTp == types.ETInt)
|
|
}
|
|
|
|
argTps := make([]types.EvalType, len(args))
|
|
argTp := types.ETReal
|
|
if isAllString {
|
|
argTp = types.ETString
|
|
} else if isAllNumber {
|
|
argTp = types.ETInt
|
|
}
|
|
for i, length := 0, len(args); i < length; i++ {
|
|
argTps[i] = argTp
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var sig builtinFunc
|
|
switch argTp {
|
|
case types.ETReal:
|
|
sig = &builtinFieldRealSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_FieldReal)
|
|
case types.ETInt:
|
|
sig = &builtinFieldIntSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_FieldInt)
|
|
case types.ETString:
|
|
sig = &builtinFieldStringSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_FieldString)
|
|
}
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinFieldIntSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinFieldIntSig) Clone() builtinFunc {
|
|
newSig := &builtinFieldIntSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals FIELD(str,str1,str2,str3,...).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_field
|
|
func (b *builtinFieldIntSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
str, isNull, err := b.args[0].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, err != nil, err
|
|
}
|
|
for i, length := 1, len(b.args); i < length; i++ {
|
|
stri, isNull, err := b.args[i].EvalInt(b.ctx, row)
|
|
if err != nil {
|
|
return 0, true, err
|
|
}
|
|
if !isNull && str == stri {
|
|
return int64(i), false, nil
|
|
}
|
|
}
|
|
return 0, false, nil
|
|
}
|
|
|
|
type builtinFieldRealSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinFieldRealSig) Clone() builtinFunc {
|
|
newSig := &builtinFieldRealSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals FIELD(str,str1,str2,str3,...).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_field
|
|
func (b *builtinFieldRealSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
str, isNull, err := b.args[0].EvalReal(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, err != nil, err
|
|
}
|
|
for i, length := 1, len(b.args); i < length; i++ {
|
|
stri, isNull, err := b.args[i].EvalReal(b.ctx, row)
|
|
if err != nil {
|
|
return 0, true, err
|
|
}
|
|
if !isNull && str == stri {
|
|
return int64(i), false, nil
|
|
}
|
|
}
|
|
return 0, false, nil
|
|
}
|
|
|
|
type builtinFieldStringSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinFieldStringSig) Clone() builtinFunc {
|
|
newSig := &builtinFieldStringSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals FIELD(str,str1,str2,str3,...).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_field
|
|
func (b *builtinFieldStringSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, err != nil, err
|
|
}
|
|
for i, length := 1, len(b.args); i < length; i++ {
|
|
stri, isNull, err := b.args[i].EvalString(b.ctx, row)
|
|
if err != nil {
|
|
return 0, true, err
|
|
}
|
|
if !isNull && b.ctor.Compare(str, stri) == 0 {
|
|
return int64(i), false, nil
|
|
}
|
|
}
|
|
return 0, false, nil
|
|
}
|
|
|
|
type makeSetFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *makeSetFunctionClass) getFlen(ctx sessionctx.Context, args []Expression) int {
|
|
flen, count := 0, 0
|
|
if constant, ok := args[0].(*Constant); ok {
|
|
bits, isNull, err := constant.EvalInt(ctx, chunk.Row{})
|
|
if err == nil && !isNull {
|
|
for i, length := 1, len(args); i < length; i++ {
|
|
if (bits & (1 << uint(i-1))) != 0 {
|
|
flen += args[i].GetType().Flen
|
|
count++
|
|
}
|
|
}
|
|
if count > 0 {
|
|
flen += count - 1
|
|
}
|
|
return flen
|
|
}
|
|
}
|
|
for i, length := 1, len(args); i < length; i++ {
|
|
flen += args[i].GetType().Flen
|
|
}
|
|
return flen + len(args) - 1 - 1
|
|
}
|
|
|
|
func (c *makeSetFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
argTps := make([]types.EvalType, len(args))
|
|
argTps[0] = types.ETInt
|
|
for i, length := 1, len(args); i < length; i++ {
|
|
argTps[i] = types.ETString
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i, length := 0, len(args); i < length; i++ {
|
|
SetBinFlagOrBinStr(args[i].GetType(), bf.tp)
|
|
}
|
|
bf.tp.Flen = c.getFlen(bf.ctx, args)
|
|
if bf.tp.Flen > mysql.MaxBlobWidth {
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
}
|
|
sig := &builtinMakeSetSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_MakeSet)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinMakeSetSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinMakeSetSig) Clone() builtinFunc {
|
|
newSig := &builtinMakeSetSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals MAKE_SET(bits,str1,str2,...).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_make-set
|
|
func (b *builtinMakeSetSig) evalString(row chunk.Row) (string, bool, error) {
|
|
bits, isNull, err := b.args[0].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
sets := make([]string, 0, len(b.args)-1)
|
|
for i, length := 1, len(b.args); i < length; i++ {
|
|
if (bits & (1 << uint(i-1))) == 0 {
|
|
continue
|
|
}
|
|
str, isNull, err := b.args[i].EvalString(b.ctx, row)
|
|
if err != nil {
|
|
return "", true, err
|
|
}
|
|
if !isNull {
|
|
sets = append(sets, str)
|
|
}
|
|
}
|
|
|
|
return strings.Join(sets, ","), false, nil
|
|
}
|
|
|
|
type octFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *octFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
var sig builtinFunc
|
|
if IsBinaryLiteral(args[0]) || args[0].GetType().EvalType() == types.ETInt {
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETInt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Charset, bf.tp.Collate = ctx.GetSessionVars().GetCharsetInfo()
|
|
bf.tp.Flen, bf.tp.Decimal = 64, types.UnspecifiedLength
|
|
sig = &builtinOctIntSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_OctInt)
|
|
} else {
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Charset, bf.tp.Collate = ctx.GetSessionVars().GetCharsetInfo()
|
|
bf.tp.Flen, bf.tp.Decimal = 64, types.UnspecifiedLength
|
|
sig = &builtinOctStringSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_OctString)
|
|
}
|
|
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinOctIntSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinOctIntSig) Clone() builtinFunc {
|
|
newSig := &builtinOctIntSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals OCT(N).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_oct
|
|
func (b *builtinOctIntSig) evalString(row chunk.Row) (string, bool, error) {
|
|
val, isNull, err := b.args[0].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", isNull, err
|
|
}
|
|
|
|
return strconv.FormatUint(uint64(val), 8), false, nil
|
|
}
|
|
|
|
type builtinOctStringSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinOctStringSig) Clone() builtinFunc {
|
|
newSig := &builtinOctStringSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals OCT(N).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_oct
|
|
func (b *builtinOctStringSig) evalString(row chunk.Row) (string, bool, error) {
|
|
val, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", isNull, err
|
|
}
|
|
|
|
negative, overflow := false, false
|
|
val = getValidPrefix(strings.TrimSpace(val), 10)
|
|
if len(val) == 0 {
|
|
return "0", false, nil
|
|
}
|
|
|
|
if val[0] == '-' {
|
|
negative, val = true, val[1:]
|
|
}
|
|
numVal, err := strconv.ParseUint(val, 10, 64)
|
|
if err != nil {
|
|
numError, ok := err.(*strconv.NumError)
|
|
if !ok || numError.Err != strconv.ErrRange {
|
|
return "", true, errors.Trace(err)
|
|
}
|
|
overflow = true
|
|
}
|
|
if negative && !overflow {
|
|
numVal = -numVal
|
|
}
|
|
return strconv.FormatUint(numVal, 8), false, nil
|
|
}
|
|
|
|
type ordFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *ordFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = 10
|
|
sig := &builtinOrdSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Ord)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinOrdSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinOrdSig) Clone() builtinFunc {
|
|
newSig := &builtinOrdSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals a builtinOrdSig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_ord
|
|
func (b *builtinOrdSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return 0, isNull, err
|
|
}
|
|
|
|
ord, err := chooseOrdFunc(b.args[0].GetType().Charset)
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
return ord(str), false, nil
|
|
}
|
|
|
|
func chooseOrdFunc(charSet string) (func(string) int64, error) {
|
|
// use utf8 by default
|
|
if charSet == "" {
|
|
charSet = charset.CharsetUTF8
|
|
}
|
|
desc, err := charset.GetCharsetDesc(charSet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if desc.Maxlen == 1 {
|
|
return ordSingleByte, nil
|
|
}
|
|
return ordUtf8, nil
|
|
}
|
|
|
|
func ordSingleByte(str string) int64 {
|
|
if len(str) == 0 {
|
|
return 0
|
|
}
|
|
return int64(str[0])
|
|
}
|
|
|
|
func ordUtf8(str string) int64 {
|
|
if len(str) == 0 {
|
|
return 0
|
|
}
|
|
_, size := utf8.DecodeRuneInString(str)
|
|
leftMost := str[:size]
|
|
var result int64
|
|
var factor int64 = 1
|
|
for i := len(leftMost) - 1; i >= 0; i-- {
|
|
result += int64(leftMost[i]) * factor
|
|
factor *= 256
|
|
}
|
|
return result
|
|
}
|
|
|
|
type quoteFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *quoteFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
SetBinFlagOrBinStr(args[0].GetType(), bf.tp)
|
|
bf.tp.Flen = 2*args[0].GetType().Flen + 2
|
|
if bf.tp.Flen > mysql.MaxBlobWidth {
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
}
|
|
sig := &builtinQuoteSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Quote)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinQuoteSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinQuoteSig) Clone() builtinFunc {
|
|
newSig := &builtinQuoteSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals QUOTE(str).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_quote
|
|
func (b *builtinQuoteSig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if err != nil {
|
|
return "", true, err
|
|
} else if isNull {
|
|
// If the argument is NULL, the return value is the word "NULL" without enclosing single quotation marks. see ref.
|
|
return "NULL", false, err
|
|
}
|
|
|
|
return Quote(str), false, nil
|
|
}
|
|
|
|
// Quote produce a result that can be used as a properly escaped data value in an SQL statement.
|
|
func Quote(str string) string {
|
|
runes := []rune(str)
|
|
buffer := bytes.NewBufferString("")
|
|
buffer.WriteRune('\'')
|
|
for i, runeLength := 0, len(runes); i < runeLength; i++ {
|
|
switch runes[i] {
|
|
case '\\', '\'':
|
|
buffer.WriteRune('\\')
|
|
buffer.WriteRune(runes[i])
|
|
case 0:
|
|
buffer.WriteRune('\\')
|
|
buffer.WriteRune('0')
|
|
case '\032':
|
|
buffer.WriteRune('\\')
|
|
buffer.WriteRune('Z')
|
|
default:
|
|
buffer.WriteRune(runes[i])
|
|
}
|
|
}
|
|
buffer.WriteRune('\'')
|
|
|
|
return buffer.String()
|
|
}
|
|
|
|
type binFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *binFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETInt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Charset, bf.tp.Collate = ctx.GetSessionVars().GetCharsetInfo()
|
|
bf.tp.Flen = 64
|
|
sig := &builtinBinSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Bin)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinBinSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinBinSig) Clone() builtinFunc {
|
|
newSig := &builtinBinSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals BIN(N).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_bin
|
|
func (b *builtinBinSig) evalString(row chunk.Row) (string, bool, error) {
|
|
val, isNull, err := b.args[0].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
return fmt.Sprintf("%b", uint64(val)), false, nil
|
|
}
|
|
|
|
type eltFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *eltFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if argsErr := c.verifyArgs(args); argsErr != nil {
|
|
return nil, argsErr
|
|
}
|
|
argTps := make([]types.EvalType, 0, len(args))
|
|
argTps = append(argTps, types.ETInt)
|
|
for i := 1; i < len(args); i++ {
|
|
argTps = append(argTps, types.ETString)
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, arg := range args[1:] {
|
|
argType := arg.GetType()
|
|
if types.IsBinaryStr(argType) {
|
|
types.SetBinChsClnFlag(bf.tp)
|
|
}
|
|
if argType.Flen > bf.tp.Flen {
|
|
bf.tp.Flen = argType.Flen
|
|
}
|
|
}
|
|
sig := &builtinEltSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Elt)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinEltSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinEltSig) Clone() builtinFunc {
|
|
newSig := &builtinEltSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a ELT(N,str1,str2,str3,...).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_elt
|
|
func (b *builtinEltSig) evalString(row chunk.Row) (string, bool, error) {
|
|
idx, isNull, err := b.args[0].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
if idx < 1 || idx >= int64(len(b.args)) {
|
|
return "", true, nil
|
|
}
|
|
arg, isNull, err := b.args[idx].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
return arg, false, nil
|
|
}
|
|
|
|
type exportSetFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *exportSetFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (sig builtinFunc, err error) {
|
|
if err = c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
argTps := make([]types.EvalType, 0, 5)
|
|
argTps = append(argTps, types.ETInt, types.ETString, types.ETString)
|
|
if len(args) > 3 {
|
|
argTps = append(argTps, types.ETString)
|
|
}
|
|
if len(args) > 4 {
|
|
argTps = append(argTps, types.ETInt)
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
switch len(args) {
|
|
case 3:
|
|
sig = &builtinExportSet3ArgSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_ExportSet3Arg)
|
|
case 4:
|
|
sig = &builtinExportSet4ArgSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_ExportSet4Arg)
|
|
case 5:
|
|
sig = &builtinExportSet5ArgSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_ExportSet5Arg)
|
|
}
|
|
return sig, nil
|
|
}
|
|
|
|
// exportSet evals EXPORT_SET(bits,on,off,separator,number_of_bits).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_export-set
|
|
func exportSet(bits int64, on, off, separator string, numberOfBits int64) string {
|
|
result := ""
|
|
for i := uint64(0); i < uint64(numberOfBits); i++ {
|
|
if (bits & (1 << i)) > 0 {
|
|
result += on
|
|
} else {
|
|
result += off
|
|
}
|
|
if i < uint64(numberOfBits)-1 {
|
|
result += separator
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
type builtinExportSet3ArgSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinExportSet3ArgSig) Clone() builtinFunc {
|
|
newSig := &builtinExportSet3ArgSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals EXPORT_SET(bits,on,off).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_export-set
|
|
func (b *builtinExportSet3ArgSig) evalString(row chunk.Row) (string, bool, error) {
|
|
bits, isNull, err := b.args[0].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
on, isNull, err := b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
off, isNull, err := b.args[2].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
return exportSet(bits, on, off, ",", 64), false, nil
|
|
}
|
|
|
|
type builtinExportSet4ArgSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinExportSet4ArgSig) Clone() builtinFunc {
|
|
newSig := &builtinExportSet4ArgSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals EXPORT_SET(bits,on,off,separator).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_export-set
|
|
func (b *builtinExportSet4ArgSig) evalString(row chunk.Row) (string, bool, error) {
|
|
bits, isNull, err := b.args[0].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
on, isNull, err := b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
off, isNull, err := b.args[2].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
separator, isNull, err := b.args[3].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
return exportSet(bits, on, off, separator, 64), false, nil
|
|
}
|
|
|
|
type builtinExportSet5ArgSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinExportSet5ArgSig) Clone() builtinFunc {
|
|
newSig := &builtinExportSet5ArgSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals EXPORT_SET(bits,on,off,separator,number_of_bits).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_export-set
|
|
func (b *builtinExportSet5ArgSig) evalString(row chunk.Row) (string, bool, error) {
|
|
bits, isNull, err := b.args[0].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
on, isNull, err := b.args[1].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
off, isNull, err := b.args[2].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
separator, isNull, err := b.args[3].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
numberOfBits, isNull, err := b.args[4].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
if numberOfBits < 0 || numberOfBits > 64 {
|
|
numberOfBits = 64
|
|
}
|
|
|
|
return exportSet(bits, on, off, separator, numberOfBits), false, nil
|
|
}
|
|
|
|
type formatFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *formatFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
argTps := make([]types.EvalType, 2, 3)
|
|
argTps[1] = types.ETInt
|
|
argTp := args[0].GetType().EvalType()
|
|
if argTp == types.ETDecimal || argTp == types.ETInt {
|
|
argTps[0] = types.ETDecimal
|
|
} else {
|
|
argTps[0] = types.ETReal
|
|
}
|
|
if len(args) == 3 {
|
|
argTps = append(argTps, types.ETString)
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Charset, bf.tp.Collate = ctx.GetSessionVars().GetCharsetInfo()
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
var sig builtinFunc
|
|
if len(args) == 3 {
|
|
sig = &builtinFormatWithLocaleSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_FormatWithLocale)
|
|
} else {
|
|
sig = &builtinFormatSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Format)
|
|
}
|
|
return sig, nil
|
|
}
|
|
|
|
// formatMaxDecimals limits the maximum number of decimal digits for result of
|
|
// function `format`, this value is same as `FORMAT_MAX_DECIMALS` in MySQL source code.
|
|
const formatMaxDecimals int64 = 30
|
|
|
|
// evalNumDecArgsForFormat evaluates first 2 arguments, i.e, x and d, for function `format`.
|
|
func evalNumDecArgsForFormat(f builtinFunc, row chunk.Row) (string, string, bool, error) {
|
|
var xStr string
|
|
arg0, arg1 := f.getArgs()[0], f.getArgs()[1]
|
|
ctx := f.getCtx()
|
|
if arg0.GetType().EvalType() == types.ETDecimal {
|
|
x, isNull, err := arg0.EvalDecimal(ctx, row)
|
|
if isNull || err != nil {
|
|
return "", "", isNull, err
|
|
}
|
|
xStr = x.String()
|
|
} else {
|
|
x, isNull, err := arg0.EvalReal(ctx, row)
|
|
if isNull || err != nil {
|
|
return "", "", isNull, err
|
|
}
|
|
xStr = strconv.FormatFloat(x, 'f', -1, 64)
|
|
}
|
|
d, isNull, err := arg1.EvalInt(ctx, row)
|
|
if isNull || err != nil {
|
|
return "", "", isNull, err
|
|
}
|
|
if d < 0 {
|
|
d = 0
|
|
} else if d > formatMaxDecimals {
|
|
d = formatMaxDecimals
|
|
}
|
|
xStr = roundFormatArgs(xStr, int(d))
|
|
dStr := strconv.FormatInt(d, 10)
|
|
return xStr, dStr, false, nil
|
|
}
|
|
|
|
func roundFormatArgs(xStr string, maxNumDecimals int) string {
|
|
if !strings.Contains(xStr, ".") {
|
|
return xStr
|
|
}
|
|
|
|
sign := false
|
|
// xStr cannot have '+' prefix now.
|
|
// It is built in `evalNumDecArgsFormat` after evaluating `Evalxxx` method.
|
|
if strings.HasPrefix(xStr, "-") {
|
|
xStr = strings.Trim(xStr, "-")
|
|
sign = true
|
|
}
|
|
|
|
xArr := strings.Split(xStr, ".")
|
|
integerPart := xArr[0]
|
|
decimalPart := xArr[1]
|
|
|
|
if len(decimalPart) > maxNumDecimals {
|
|
t := []byte(decimalPart)
|
|
carry := false
|
|
if t[maxNumDecimals] >= '5' {
|
|
carry = true
|
|
}
|
|
for i := maxNumDecimals - 1; i >= 0 && carry; i-- {
|
|
if t[i] == '9' {
|
|
t[i] = '0'
|
|
} else {
|
|
t[i] = t[i] + 1
|
|
carry = false
|
|
}
|
|
}
|
|
decimalPart = string(t)
|
|
t = []byte(integerPart)
|
|
for i := len(integerPart) - 1; i >= 0 && carry; i-- {
|
|
if t[i] == '9' {
|
|
t[i] = '0'
|
|
} else {
|
|
t[i] = t[i] + 1
|
|
carry = false
|
|
}
|
|
}
|
|
if carry {
|
|
integerPart = "1" + string(t)
|
|
} else {
|
|
integerPart = string(t)
|
|
}
|
|
}
|
|
|
|
xStr = integerPart + "." + decimalPart
|
|
if sign {
|
|
xStr = "-" + xStr
|
|
}
|
|
return xStr
|
|
}
|
|
|
|
type builtinFormatWithLocaleSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinFormatWithLocaleSig) Clone() builtinFunc {
|
|
newSig := &builtinFormatWithLocaleSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals FORMAT(X,D,locale).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_format
|
|
func (b *builtinFormatWithLocaleSig) evalString(row chunk.Row) (string, bool, error) {
|
|
x, d, isNull, err := evalNumDecArgsForFormat(b, row)
|
|
if isNull || err != nil {
|
|
return "", isNull, err
|
|
}
|
|
locale, isNull, err := b.args[2].EvalString(b.ctx, row)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
if isNull {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errUnknownLocale.GenWithStackByArgs("NULL"))
|
|
locale = "en_US"
|
|
}
|
|
formatString, err := mysql.GetLocaleFormatFunction(locale)(x, d)
|
|
return formatString, false, err
|
|
}
|
|
|
|
type builtinFormatSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinFormatSig) Clone() builtinFunc {
|
|
newSig := &builtinFormatSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals FORMAT(X,D).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_format
|
|
func (b *builtinFormatSig) evalString(row chunk.Row) (string, bool, error) {
|
|
x, d, isNull, err := evalNumDecArgsForFormat(b, row)
|
|
if isNull || err != nil {
|
|
return "", isNull, err
|
|
}
|
|
formatString, err := mysql.GetLocaleFormatFunction("en_US")(x, d)
|
|
return formatString, false, err
|
|
}
|
|
|
|
type fromBase64FunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *fromBase64FunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
|
|
valStr, _ := ctx.GetSessionVars().GetSystemVar(variable.MaxAllowedPacket)
|
|
maxAllowedPacket, err := strconv.ParseUint(valStr, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
|
|
types.SetBinChsClnFlag(bf.tp)
|
|
sig := &builtinFromBase64Sig{bf, maxAllowedPacket}
|
|
return sig, nil
|
|
}
|
|
|
|
// base64NeededDecodedLength return the base64 decoded string length.
|
|
func base64NeededDecodedLength(n int) int {
|
|
// Returns -1 indicate the result will overflow.
|
|
if strconv.IntSize == 64 && n > math.MaxInt64/3 {
|
|
return -1
|
|
}
|
|
if strconv.IntSize == 32 && n > math.MaxInt32/3 {
|
|
return -1
|
|
}
|
|
return n * 3 / 4
|
|
}
|
|
|
|
type builtinFromBase64Sig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinFromBase64Sig) Clone() builtinFunc {
|
|
newSig := &builtinFromBase64Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals FROM_BASE64(str).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_from-base64
|
|
func (b *builtinFromBase64Sig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
needDecodeLen := base64NeededDecodedLength(len(str))
|
|
if needDecodeLen == -1 {
|
|
return "", true, nil
|
|
}
|
|
if needDecodeLen > int(b.maxAllowedPacket) {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("from_base64", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
|
|
str = strings.Replace(str, "\t", "", -1)
|
|
str = strings.Replace(str, " ", "", -1)
|
|
result, err := base64.StdEncoding.DecodeString(str)
|
|
if err != nil {
|
|
// When error happens, take `from_base64("asc")` as an example, we should return NULL.
|
|
return "", true, nil
|
|
}
|
|
return string(result), false, nil
|
|
}
|
|
|
|
type toBase64FunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *toBase64FunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Charset, bf.tp.Collate = ctx.GetSessionVars().GetCharsetInfo()
|
|
bf.tp.Flen = base64NeededEncodedLength(bf.args[0].GetType().Flen)
|
|
|
|
valStr, _ := ctx.GetSessionVars().GetSystemVar(variable.MaxAllowedPacket)
|
|
maxAllowedPacket, err := strconv.ParseUint(valStr, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
|
|
sig := &builtinToBase64Sig{bf, maxAllowedPacket}
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinToBase64Sig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinToBase64Sig) Clone() builtinFunc {
|
|
newSig := &builtinToBase64Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// base64NeededEncodedLength return the base64 encoded string length.
|
|
func base64NeededEncodedLength(n int) int {
|
|
// Returns -1 indicate the result will overflow.
|
|
if strconv.IntSize == 64 {
|
|
// len(arg) -> len(to_base64(arg))
|
|
// 6827690988321067803 -> 9223372036854775804
|
|
// 6827690988321067804 -> -9223372036854775808
|
|
if n > 6827690988321067803 {
|
|
return -1
|
|
}
|
|
} else {
|
|
// len(arg) -> len(to_base64(arg))
|
|
// 1589695686 -> 2147483645
|
|
// 1589695687 -> -2147483646
|
|
if n > 1589695686 {
|
|
return -1
|
|
}
|
|
}
|
|
|
|
length := (n + 2) / 3 * 4
|
|
return length + (length-1)/76
|
|
}
|
|
|
|
// evalString evals a builtinToBase64Sig.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_to-base64
|
|
func (b *builtinToBase64Sig) evalString(row chunk.Row) (d string, isNull bool, err error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", isNull, err
|
|
}
|
|
needEncodeLen := base64NeededEncodedLength(len(str))
|
|
if needEncodeLen == -1 {
|
|
return "", true, nil
|
|
}
|
|
if needEncodeLen > int(b.maxAllowedPacket) {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("to_base64", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
if b.tp.Flen == -1 || b.tp.Flen > mysql.MaxBlobWidth {
|
|
return "", true, nil
|
|
}
|
|
|
|
//encode
|
|
strBytes := []byte(str)
|
|
result := base64.StdEncoding.EncodeToString(strBytes)
|
|
//A newline is added after each 76 characters of encoded output to divide long output into multiple lines.
|
|
count := len(result)
|
|
if count > 76 {
|
|
resultArr := splitToSubN(result, 76)
|
|
result = strings.Join(resultArr, "\n")
|
|
}
|
|
|
|
return result, false, nil
|
|
}
|
|
|
|
// splitToSubN splits a string every n runes into a string[]
|
|
func splitToSubN(s string, n int) []string {
|
|
subs := make([]string, 0, len(s)/n+1)
|
|
for len(s) > n {
|
|
subs = append(subs, s[:n])
|
|
s = s[n:]
|
|
}
|
|
subs = append(subs, s)
|
|
return subs
|
|
}
|
|
|
|
type insertFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *insertFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (sig builtinFunc, err error) {
|
|
if err = c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, types.ETString, types.ETInt, types.ETInt, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = mysql.MaxBlobWidth
|
|
SetBinFlagOrBinStr(args[0].GetType(), bf.tp)
|
|
SetBinFlagOrBinStr(args[3].GetType(), bf.tp)
|
|
|
|
valStr, _ := ctx.GetSessionVars().GetSystemVar(variable.MaxAllowedPacket)
|
|
maxAllowedPacket, err := strconv.ParseUint(valStr, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
|
|
if types.IsBinaryStr(args[0].GetType()) {
|
|
sig = &builtinInsertSig{bf, maxAllowedPacket}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Insert)
|
|
} else {
|
|
sig = &builtinInsertUTF8Sig{bf, maxAllowedPacket}
|
|
sig.setPbCode(tipb.ScalarFuncSig_InsertUTF8)
|
|
}
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinInsertSig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinInsertSig) Clone() builtinFunc {
|
|
newSig := &builtinInsertSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals INSERT(str,pos,len,newstr).
|
|
// See https://dev.mysql.com/doc/refman/5.6/en/string-functions.html#function_insert
|
|
func (b *builtinInsertSig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
pos, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
length, isNull, err := b.args[2].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
newstr, isNull, err := b.args[3].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
strLength := int64(len(str))
|
|
if pos < 1 || pos > strLength {
|
|
return str, false, nil
|
|
}
|
|
if length > strLength-pos+1 || length < 0 {
|
|
length = strLength - pos + 1
|
|
}
|
|
|
|
if uint64(strLength-length+int64(len(newstr))) > b.maxAllowedPacket {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("insert", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
|
|
return str[0:pos-1] + newstr + str[pos+length-1:], false, nil
|
|
}
|
|
|
|
type builtinInsertUTF8Sig struct {
|
|
baseBuiltinFunc
|
|
maxAllowedPacket uint64
|
|
}
|
|
|
|
func (b *builtinInsertUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinInsertUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.maxAllowedPacket = b.maxAllowedPacket
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals INSERT(str,pos,len,newstr).
|
|
// See https://dev.mysql.com/doc/refman/5.6/en/string-functions.html#function_insert
|
|
func (b *builtinInsertUTF8Sig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
pos, isNull, err := b.args[1].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
length, isNull, err := b.args[2].EvalInt(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
newstr, isNull, err := b.args[3].EvalString(b.ctx, row)
|
|
if isNull || err != nil {
|
|
return "", true, err
|
|
}
|
|
|
|
runes := []rune(str)
|
|
runeLength := int64(len(runes))
|
|
if pos < 1 || pos > runeLength {
|
|
return str, false, nil
|
|
}
|
|
if length > runeLength-pos+1 || length < 0 {
|
|
length = runeLength - pos + 1
|
|
}
|
|
|
|
strHead := string(runes[0 : pos-1])
|
|
strTail := string(runes[pos+length-1:])
|
|
if uint64(len(strHead)+len(newstr)+len(strTail)) > b.maxAllowedPacket {
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errWarnAllowedPacketOverflowed.GenWithStackByArgs("insert", b.maxAllowedPacket))
|
|
return "", true, nil
|
|
}
|
|
return strHead + newstr + strTail, false, nil
|
|
}
|
|
|
|
type instrFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *instrFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
if err := c.verifyArgs(args); err != nil {
|
|
return nil, err
|
|
}
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, types.ETString, types.ETString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bf.tp.Flen = 11
|
|
if bf.collation == charset.CollationBin {
|
|
sig := &builtinInstrSig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_Instr)
|
|
return sig, nil
|
|
}
|
|
sig := &builtinInstrUTF8Sig{bf}
|
|
sig.setPbCode(tipb.ScalarFuncSig_InstrUTF8)
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinInstrUTF8Sig struct{ baseBuiltinFunc }
|
|
|
|
func (b *builtinInstrUTF8Sig) Clone() builtinFunc {
|
|
newSig := &builtinInstrUTF8Sig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
type builtinInstrSig struct{ baseBuiltinFunc }
|
|
|
|
func (b *builtinInstrSig) Clone() builtinFunc {
|
|
newSig := &builtinInstrSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalInt evals INSTR(str,substr).
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_instr
|
|
func (b *builtinInstrUTF8Sig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
str, IsNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if IsNull || err != nil {
|
|
return 0, true, err
|
|
}
|
|
substr, IsNull, err := b.args[1].EvalString(b.ctx, row)
|
|
if IsNull || err != nil {
|
|
return 0, true, err
|
|
}
|
|
if collate.IsCICollation(b.collation) {
|
|
str = strings.ToLower(str)
|
|
substr = strings.ToLower(substr)
|
|
}
|
|
|
|
idx := strings.Index(str, substr)
|
|
if idx == -1 {
|
|
return 0, false, nil
|
|
}
|
|
return int64(utf8.RuneCountInString(str[:idx]) + 1), false, nil
|
|
}
|
|
|
|
// evalInt evals INSTR(str,substr), case sensitive.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_instr
|
|
func (b *builtinInstrSig) evalInt(row chunk.Row) (int64, bool, error) {
|
|
str, IsNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if IsNull || err != nil {
|
|
return 0, true, err
|
|
}
|
|
|
|
substr, IsNull, err := b.args[1].EvalString(b.ctx, row)
|
|
if IsNull || err != nil {
|
|
return 0, true, err
|
|
}
|
|
|
|
idx := strings.Index(str, substr)
|
|
if idx == -1 {
|
|
return 0, false, nil
|
|
}
|
|
return int64(idx + 1), false, nil
|
|
}
|
|
|
|
type loadFileFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *loadFileFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
return nil, errFunctionNotExists.GenWithStackByArgs("FUNCTION", "load_file")
|
|
}
|
|
|
|
type weightStringPadding byte
|
|
|
|
const (
|
|
// weightStringPaddingNone is used for WEIGHT_STRING(expr) if the expr is non-numeric.
|
|
weightStringPaddingNone weightStringPadding = iota
|
|
// weightStringPaddingAsChar is used for WEIGHT_STRING(expr AS CHAR(x)) and the expr is non-numeric.
|
|
weightStringPaddingAsChar
|
|
// weightStringPaddingAsBinary is used for WEIGHT_STRING(expr as BINARY(x)) and the expr is not null.
|
|
weightStringPaddingAsBinary
|
|
// weightStringPaddingNull is used for WEIGHT_STRING(expr [AS (CHAR|BINARY)]) for all other cases, it returns null always.
|
|
weightStringPaddingNull
|
|
)
|
|
|
|
type weightStringFunctionClass struct {
|
|
baseFunctionClass
|
|
}
|
|
|
|
func (c *weightStringFunctionClass) verifyArgs(args []Expression) (weightStringPadding, int, error) {
|
|
padding := weightStringPaddingNone
|
|
l := len(args)
|
|
if l != 1 && l != 3 {
|
|
return weightStringPaddingNone, 0, ErrIncorrectParameterCount.GenWithStackByArgs(c.funcName)
|
|
}
|
|
if types.IsTypeNumeric(args[0].GetType().Tp) {
|
|
padding = weightStringPaddingNull
|
|
}
|
|
length := 0
|
|
if l == 3 {
|
|
if args[1].GetType().EvalType() != types.ETString {
|
|
return weightStringPaddingNone, 0, ErrIncorrectType.GenWithStackByArgs(args[1].String(), c.funcName)
|
|
}
|
|
c1, ok := args[1].(*Constant)
|
|
if !ok {
|
|
return weightStringPaddingNone, 0, ErrIncorrectType.GenWithStackByArgs(args[1].String(), c.funcName)
|
|
}
|
|
switch x := c1.Value.GetString(); x {
|
|
case "CHAR":
|
|
if padding == weightStringPaddingNone {
|
|
padding = weightStringPaddingAsChar
|
|
}
|
|
case "BINARY":
|
|
padding = weightStringPaddingAsBinary
|
|
default:
|
|
return weightStringPaddingNone, 0, ErrIncorrectType.GenWithStackByArgs(x, c.funcName)
|
|
}
|
|
if args[2].GetType().EvalType() != types.ETInt {
|
|
return weightStringPaddingNone, 0, ErrIncorrectType.GenWithStackByArgs(args[2].String(), c.funcName)
|
|
}
|
|
c2, ok := args[2].(*Constant)
|
|
if !ok {
|
|
return weightStringPaddingNone, 0, ErrIncorrectType.GenWithStackByArgs(args[1].String(), c.funcName)
|
|
}
|
|
length = int(c2.Value.GetInt64())
|
|
if length == 0 {
|
|
return weightStringPaddingNone, 0, ErrIncorrectType.GenWithStackByArgs(args[2].String(), c.funcName)
|
|
}
|
|
}
|
|
return padding, length, nil
|
|
}
|
|
|
|
func (c *weightStringFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
|
|
padding, length, err := c.verifyArgs(args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argTps := make([]types.EvalType, len(args))
|
|
argTps[0] = types.ETString
|
|
|
|
if len(args) == 3 {
|
|
argTps[1] = types.ETString
|
|
argTps[2] = types.ETInt
|
|
}
|
|
|
|
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETString, argTps...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var sig builtinFunc
|
|
if padding == weightStringPaddingNull {
|
|
sig = &builtinWeightStringNullSig{bf}
|
|
} else {
|
|
sig = &builtinWeightStringSig{bf, padding, length}
|
|
}
|
|
return sig, nil
|
|
}
|
|
|
|
type builtinWeightStringNullSig struct {
|
|
baseBuiltinFunc
|
|
}
|
|
|
|
func (b *builtinWeightStringNullSig) Clone() builtinFunc {
|
|
newSig := &builtinWeightStringNullSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a WEIGHT_STRING(expr [AS CHAR|BINARY]) when the expr is numeric types, it always returns null.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_weight-string
|
|
func (b *builtinWeightStringNullSig) evalString(row chunk.Row) (string, bool, error) {
|
|
return "", true, nil
|
|
}
|
|
|
|
type builtinWeightStringSig struct {
|
|
baseBuiltinFunc
|
|
|
|
padding weightStringPadding
|
|
length int
|
|
}
|
|
|
|
func (b *builtinWeightStringSig) Clone() builtinFunc {
|
|
newSig := &builtinWeightStringSig{}
|
|
newSig.cloneFrom(&b.baseBuiltinFunc)
|
|
newSig.padding = b.padding
|
|
newSig.length = b.length
|
|
return newSig
|
|
}
|
|
|
|
// evalString evals a WEIGHT_STRING(expr [AS (CHAR|BINARY)]) when the expr is non-numeric types.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_weight-string
|
|
func (b *builtinWeightStringSig) evalString(row chunk.Row) (string, bool, error) {
|
|
str, isNull, err := b.args[0].EvalString(b.ctx, row)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
if isNull {
|
|
return "", true, nil
|
|
}
|
|
|
|
var ctor collate.Collator
|
|
// TODO: refactor padding codes after padding is implemented by all collators.
|
|
switch b.padding {
|
|
case weightStringPaddingAsChar:
|
|
runes := []rune(str)
|
|
lenRunes := len(runes)
|
|
if b.length < lenRunes {
|
|
str = string(runes[:b.length])
|
|
} else if b.length > lenRunes {
|
|
str += strings.Repeat(" ", b.length-lenRunes)
|
|
}
|
|
ctor = collate.GetCollator(b.args[0].GetType().Collate)
|
|
case weightStringPaddingAsBinary:
|
|
lenStr := len(str)
|
|
if b.length < lenStr {
|
|
tpInfo := fmt.Sprintf("BINARY(%d)", b.length)
|
|
b.ctx.GetSessionVars().StmtCtx.AppendWarning(errTruncatedWrongValue.GenWithStackByArgs(tpInfo, str))
|
|
str = str[:b.length]
|
|
} else if b.length > lenStr {
|
|
str += strings.Repeat("\x00", b.length-lenStr)
|
|
}
|
|
ctor = collate.GetCollator(charset.CollationBin)
|
|
case weightStringPaddingNone:
|
|
ctor = collate.GetCollator(b.args[0].GetType().Collate)
|
|
default:
|
|
return "", false, ErrIncorrectType.GenWithStackByArgs(ast.WeightString, string(b.padding))
|
|
}
|
|
return string(ctor.Key(str)), false, nil
|
|
}
|
|
|