Files
tidb/pkg/expression/builtin_string_test.go

2886 lines
88 KiB
Go

// 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,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"fmt"
"strconv"
"strings"
"testing"
"time"
"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/errno"
"github.com/pingcap/tidb/pkg/parser/ast"
"github.com/pingcap/tidb/pkg/parser/charset"
"github.com/pingcap/tidb/pkg/parser/mysql"
"github.com/pingcap/tidb/pkg/parser/terror"
"github.com/pingcap/tidb/pkg/sessionctx/vardef"
"github.com/pingcap/tidb/pkg/testkit/testutil"
"github.com/pingcap/tidb/pkg/types"
"github.com/pingcap/tidb/pkg/util/chunk"
contextutil "github.com/pingcap/tidb/pkg/util/context"
"github.com/pingcap/tidb/pkg/util/mock"
"github.com/stretchr/testify/require"
)
func TestLengthAndOctetLength(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args any
expected int64
isNil bool
getErr bool
}{
{"abc", 3, false, false},
{"你好", 6, false, false},
{1, 1, false, false},
{3.14, 4, false, false},
{types.NewDecFromFloatForTest(123.123), 7, false, false},
{types.NewTime(types.FromGoTime(time.Now()), mysql.TypeDatetime, 6), 26, false, false},
{types.NewBinaryLiteralFromUint(0x01, -1), 1, false, false},
{types.Set{Value: 1, Name: "abc"}, 3, false, false},
{types.Duration{Duration: 12*time.Hour + 1*time.Minute + 1*time.Second, Fsp: types.DefaultFsp}, 8, false, false},
{nil, 0, true, false},
{errors.New("must error"), 0, false, true},
}
lengthMethods := []string{ast.Length, ast.OctetLength}
for _, lengthMethod := range lengthMethods {
for _, c := range cases {
f, err := newFunctionForTest(ctx, lengthMethod, primitiveValsToConstants(ctx, []any{c.args})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.expected, d.GetInt64())
}
}
}
}
_, err := funcs[ast.Length].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
// Test GBK String
tbl := []struct {
input string
chs string
result int64
}{
{"abc", "gbk", 3},
{"一二三", "gbk", 6},
{"一二三", "", 9},
{"一二三!", "gbk", 7},
{"一二三!", "", 10},
}
for _, lengthMethod := range lengthMethods {
for _, c := range tbl {
err := ctx.GetSessionVars().SetSystemVarWithoutValidation(vardef.CharacterSetConnection, c.chs)
require.NoError(t, err)
f, err := newFunctionForTest(ctx, lengthMethod, primitiveValsToConstants(ctx, []any{c.input})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
require.NoError(t, err)
require.Equal(t, c.result, d.GetInt64())
}
}
}
func TestASCII(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args any
expected int64
isNil bool
getErr bool
}{
{"2", 50, false, false},
{2, 50, false, false},
{"23", 50, false, false},
{23, 50, false, false},
{2.3, 50, false, false},
{nil, 0, true, false},
{"", 0, false, false},
{"你好", 228, false, false},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.ASCII, primitiveValsToConstants(ctx, []any{c.args})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.expected, d.GetInt64())
}
}
}
_, err := funcs[ast.Length].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
// Test GBK String
tbl := []struct {
input string
chs string
result int64
}{
{"abc", "gbk", 97},
{"你好", "gbk", 196},
{"你好", "", 228},
{"世界", "gbk", 202},
{"abc", "gb18030", 97},
{"你好", "gb18030", 196},
{"世界", "gb18030", 202},
}
for _, c := range tbl {
err := ctx.GetSessionVars().SetSystemVarWithoutValidation(vardef.CharacterSetConnection, c.chs)
require.NoError(t, err)
f, err := newFunctionForTest(ctx, ast.ASCII, primitiveValsToConstants(ctx, []any{c.input})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
require.NoError(t, err)
require.Equal(t, c.result, d.GetInt64())
}
}
func TestConcat(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args []any
isNil bool
getErr bool
res string
retType *types.FieldType
}{
{
[]any{nil},
true, false, "",
types.NewFieldTypeBuilder().SetType(mysql.TypeVarString).SetFlag(mysql.BinaryFlag).SetDecimal(types.UnspecifiedLength).SetCharset(charset.CharsetBin).SetCollate(charset.CollationBin).BuildP(),
},
{
[]any{"a", "b",
1, 2,
1.1, 1.2,
types.NewDecFromFloatForTest(1.1),
types.NewTime(types.FromDate(2000, 1, 1, 12, 01, 01, 0), mysql.TypeDatetime, types.DefaultFsp),
types.Duration{
Duration: 12*time.Hour + 1*time.Minute + 1*time.Second,
Fsp: types.DefaultFsp},
},
false, false, "ab121.11.21.12000-01-01 12:01:0112:01:01",
types.NewFieldTypeBuilder().SetType(mysql.TypeVarString).SetFlag(mysql.BinaryFlag).SetFlen(40).SetDecimal(types.UnspecifiedLength).SetCharset(charset.CharsetBin).SetCollate(charset.CollationBin).BuildP(),
},
{
[]any{"a", "b", nil, "c"},
true, false, "",
types.NewFieldTypeBuilder().SetType(mysql.TypeVarString).SetFlag(mysql.BinaryFlag).SetFlen(3).SetDecimal(types.UnspecifiedLength).SetCharset(charset.CharsetBin).SetCollate(charset.CollationBin).BuildP(),
},
{
[]any{errors.New("must error")},
false, true, "",
types.NewFieldTypeBuilder().SetType(mysql.TypeVarString).SetFlag(mysql.BinaryFlag).SetFlen(types.UnspecifiedLength).SetDecimal(types.UnspecifiedLength).SetCharset(charset.CharsetBin).SetCollate(charset.CollationBin).BuildP(),
},
}
fcName := ast.Concat
for _, c := range cases {
f, err := newFunctionForTest(ctx, fcName, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
v, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, v.Kind())
} else {
require.Equal(t, c.res, v.GetString())
}
}
}
}
func TestConcatSig(t *testing.T) {
ctx := createContext(t)
colTypes := []*types.FieldType{
types.NewFieldType(mysql.TypeVarchar),
types.NewFieldType(mysql.TypeVarchar),
}
resultType := &types.FieldType{}
resultType.SetType(mysql.TypeVarchar)
resultType.SetFlen(1000)
args := []Expression{
&Column{Index: 0, RetType: colTypes[0]},
&Column{Index: 1, RetType: colTypes[1]},
}
base := baseBuiltinFunc{args: args, tp: resultType}
concat := &builtinConcatSig{base, 5}
cases := []struct {
args []any
warnings int
res string
}{
{[]any{"a", "b"}, 0, "ab"},
{[]any{"aaa", "bbb"}, 1, ""},
{[]any{"中", "a"}, 0, "中a"},
{[]any{"中文", "a"}, 2, ""},
}
for _, c := range cases {
input := chunk.NewChunkWithCapacity(colTypes, 10)
input.AppendString(0, c.args[0].(string))
input.AppendString(1, c.args[1].(string))
res, isNull, err := concat.evalString(ctx, input.GetRow(0))
require.Equal(t, c.res, res)
require.NoError(t, err)
if c.warnings == 0 {
require.False(t, isNull)
} else {
require.True(t, isNull)
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Len(t, warnings, c.warnings)
lastWarn := warnings[len(warnings)-1]
require.True(t, terror.ErrorEqual(errWarnAllowedPacketOverflowed, lastWarn.Err))
}
}
}
func TestConcatWS(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args []any
isNil bool
getErr bool
expected string
}{
{
[]any{nil, nil},
true, false, "",
},
{
[]any{nil, "a", "b"},
true, false, "",
},
{
[]any{",", "a", "b", "hello", `$^%`},
false, false,
`a,b,hello,$^%`,
},
{
[]any{"|", "a", nil, "b", "c"},
false, false,
"a|b|c",
},
{
[]any{",", "a", ",", "b", "c"},
false, false,
"a,,,b,c",
},
{
[]any{errors.New("must error"), "a", "b"},
false, true, "",
},
{
[]any{",", "a", "b", 1, 2, 1.1, 0.11,
types.NewDecFromFloatForTest(1.1),
types.NewTime(types.FromDate(2000, 1, 1, 12, 01, 01, 0), mysql.TypeDatetime, types.DefaultFsp),
types.Duration{
Duration: 12*time.Hour + 1*time.Minute + 1*time.Second,
Fsp: types.DefaultFsp},
},
false, false, "a,b,1,2,1.1,0.11,1.1,2000-01-01 12:01:01,12:01:01",
},
}
fcName := ast.ConcatWS
// ERROR 1582 (42000): Incorrect parameter count in the call to native function 'concat_ws'
_, err := newFunctionForTest(ctx, fcName, primitiveValsToConstants(ctx, []any{nil})...)
require.Error(t, err)
for _, c := range cases {
f, err := newFunctionForTest(ctx, fcName, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
val, err1 := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.NotNil(t, err1)
} else {
require.Nil(t, err1)
if c.isNil {
require.Equal(t, types.KindNull, val.Kind())
} else {
require.Equal(t, c.expected, val.GetString())
}
}
}
_, err = funcs[ast.ConcatWS].getFunction(ctx, primitiveValsToConstants(ctx, []any{nil, nil}))
require.NoError(t, err)
}
func TestConcatWSSig(t *testing.T) {
ctx := createContext(t)
colTypes := []*types.FieldType{
types.NewFieldType(mysql.TypeVarchar),
types.NewFieldType(mysql.TypeVarchar),
types.NewFieldType(mysql.TypeVarchar),
}
resultType := &types.FieldType{}
resultType.SetType(mysql.TypeVarchar)
resultType.SetFlen(1000)
args := []Expression{
&Column{Index: 0, RetType: colTypes[0]},
&Column{Index: 1, RetType: colTypes[1]},
&Column{Index: 2, RetType: colTypes[2]},
}
base := baseBuiltinFunc{args: args, tp: resultType}
concat := &builtinConcatWSSig{base, 6}
cases := []struct {
args []any
warnings int
res string
}{
{[]any{",", "a", "b"}, 0, "a,b"},
{[]any{",", "aaa", "bbb"}, 1, ""},
{[]any{",", "中", "a"}, 0, "中,a"},
{[]any{",", "中文", "a"}, 2, ""},
}
for _, c := range cases {
input := chunk.NewChunkWithCapacity(colTypes, 10)
input.AppendString(0, c.args[0].(string))
input.AppendString(1, c.args[1].(string))
input.AppendString(2, c.args[2].(string))
res, isNull, err := concat.evalString(ctx, input.GetRow(0))
require.Equal(t, c.res, res)
require.NoError(t, err)
if c.warnings == 0 {
require.False(t, isNull)
} else {
require.True(t, isNull)
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Len(t, warnings, c.warnings)
lastWarn := warnings[len(warnings)-1]
require.True(t, terror.ErrorEqual(errWarnAllowedPacketOverflowed, lastWarn.Err))
}
}
}
func TestLeft(t *testing.T) {
ctx := createContext(t)
stmtCtx := ctx.GetSessionVars().StmtCtx
oldTypeFlags := stmtCtx.TypeFlags()
defer func() {
stmtCtx.SetTypeFlags(oldTypeFlags)
}()
stmtCtx.SetTypeFlags(oldTypeFlags.WithIgnoreTruncateErr(true))
cases := []struct {
args []any
isNil bool
getErr bool
res string
}{
{[]any{"abcde", 3}, false, false, "abc"},
{[]any{"abcde", 0}, false, false, ""},
{[]any{"abcde", 1.2}, false, false, "a"},
{[]any{"abcde", 1.9}, false, false, "ab"},
{[]any{"abcde", -1}, false, false, ""},
{[]any{"abcde", 100}, false, false, "abcde"},
{[]any{"abcde", nil}, true, false, ""},
{[]any{nil, 3}, true, false, ""},
{[]any{"abcde", "3"}, false, false, "abc"},
{[]any{"abcde", "a"}, false, false, ""},
{[]any{1234, 3}, false, false, "123"},
{[]any{12.34, 3}, false, false, "12."},
{[]any{types.NewBinaryLiteralFromUint(0x0102, -1), 1}, false, false, string([]byte{0x01})},
{[]any{errors.New("must err"), 0}, false, true, ""},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Left, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
v, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, v.Kind())
} else {
require.Equal(t, c.res, v.GetString())
}
}
}
_, err := funcs[ast.Left].getFunction(ctx, []Expression{getVarcharCon(), getInt8Con()})
require.NoError(t, err)
}
func TestRight(t *testing.T) {
ctx := createContext(t)
stmtCtx := ctx.GetSessionVars().StmtCtx
oldTypeFlags := stmtCtx.TypeFlags()
defer func() {
stmtCtx.SetTypeFlags(oldTypeFlags)
}()
stmtCtx.SetTypeFlags(oldTypeFlags.WithIgnoreTruncateErr(true))
cases := []struct {
args []any
isNil bool
getErr bool
res string
}{
{[]any{"abcde", 3}, false, false, "cde"},
{[]any{"abcde", 0}, false, false, ""},
{[]any{"abcde", 1.2}, false, false, "e"},
{[]any{"abcde", 1.9}, false, false, "de"},
{[]any{"abcde", -1}, false, false, ""},
{[]any{"abcde", 100}, false, false, "abcde"},
{[]any{"abcde", nil}, true, false, ""},
{[]any{nil, 1}, true, false, ""},
{[]any{"abcde", "3"}, false, false, "cde"},
{[]any{"abcde", "a"}, false, false, ""},
{[]any{1234, 3}, false, false, "234"},
{[]any{12.34, 3}, false, false, ".34"},
{[]any{types.NewBinaryLiteralFromUint(0x0102, -1), 1}, false, false, string([]byte{0x02})},
{[]any{errors.New("must err"), 0}, false, true, ""},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Right, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
v, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, v.Kind())
} else {
require.Equal(t, c.res, v.GetString())
}
}
}
_, err := funcs[ast.Right].getFunction(ctx, []Expression{getVarcharCon(), getInt8Con()})
require.NoError(t, err)
}
func TestRepeat(t *testing.T) {
cases := []struct {
args []any
isNull bool
res string
}{
{[]any{"a", int64(2)}, false, "aa"},
{[]any{"a", uint64(16777217)}, false, strings.Repeat("a", 16777217)},
{[]any{"a", int64(16777216)}, false, strings.Repeat("a", 16777216)},
{[]any{"a", int64(-1)}, false, ""},
{[]any{"a", int64(0)}, false, ""},
{[]any{"a", uint64(0)}, false, ""},
}
ctx := createContext(t)
fc := funcs[ast.Repeat]
for _, c := range cases {
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(c.args...)))
require.NoError(t, err)
v, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
if c.isNull {
require.True(t, v.IsNull())
} else {
require.Equal(t, v.GetString(), c.res)
}
}
}
func TestRepeatSig(t *testing.T) {
ctx := createContext(t)
colTypes := []*types.FieldType{
types.NewFieldType(mysql.TypeVarchar),
types.NewFieldType(mysql.TypeLonglong),
}
resultType := &types.FieldType{}
resultType.SetType(mysql.TypeVarchar)
resultType.SetFlen(1000)
args := []Expression{
&Column{Index: 0, RetType: colTypes[0]},
&Column{Index: 1, RetType: colTypes[1]},
}
base := baseBuiltinFunc{args: args, tp: resultType}
repeat := &builtinRepeatSig{base, 1000}
cases := []struct {
args []any
warning int
res string
}{
{[]any{"a", int64(6)}, 0, "aaaaaa"},
{[]any{"a", int64(10001)}, 1, ""},
{[]any{"毅", int64(6)}, 0, "毅毅毅毅毅毅"},
{[]any{"毅", int64(334)}, 2, ""},
}
for _, c := range cases {
input := chunk.NewChunkWithCapacity(colTypes, 10)
input.AppendString(0, c.args[0].(string))
input.AppendInt64(1, c.args[1].(int64))
res, isNull, err := repeat.evalString(ctx, input.GetRow(0))
require.Equal(t, c.res, res)
require.NoError(t, err)
if c.warning == 0 {
require.False(t, isNull)
} else {
require.True(t, isNull)
require.NoError(t, err)
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Len(t, warnings, c.warning)
lastWarn := warnings[len(warnings)-1]
require.True(t, terror.ErrorEqual(errWarnAllowedPacketOverflowed, lastWarn.Err))
}
}
}
func TestLower(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args []any
isNil bool
getErr bool
res string
}{
{[]any{nil}, true, false, ""},
{[]any{"ab"}, false, false, "ab"},
{[]any{1}, false, false, "1"},
{[]any{"one week’s time TEST"}, false, false, "one week’s time test"},
{[]any{"one week's time TEST"}, false, false, "one week's time test"},
{[]any{"ABC测试DEF"}, false, false, "abc测试def"},
{[]any{"ABCテストDEF"}, false, false, "abcテストdef"},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Lower, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
v, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, v.Kind())
} else {
require.Equal(t, c.res, v.GetString())
}
}
}
_, err := funcs[ast.Lower].getFunction(ctx, []Expression{getVarcharCon()})
require.NoError(t, err)
// Test GBK String
tbl := []struct {
input string
chs string
result string
}{
{"ABC", "gbk", "abc"},
{"一二三", "gbk", "一二三"},
{"àáèéêìíòóùúüāēěīńňōūǎǐǒǔǖǘǚǜⅪⅫ", "gbk", "àáèéêìíòóùúüāēěīńňōūǎǐǒǔǖǘǚǜⅪⅫ"},
{"àáèéêìíòóùúüāēěīńňōūǎǐǒǔǖǘǚǜⅪⅫ", "", "àáèéêìíòóùúüāēěīńňōūǎǐǒǔǖǘǚǜⅺⅻ"},
}
for _, c := range tbl {
err := ctx.GetSessionVars().SetSystemVarWithoutValidation(vardef.CharacterSetConnection, c.chs)
require.NoError(t, err)
f, err := newFunctionForTest(ctx, ast.Lower, primitiveValsToConstants(ctx, []any{c.input})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
require.NoError(t, err)
require.Equal(t, c.result, d.GetString())
}
}
func TestUpper(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args []any
isNil bool
getErr bool
res string
}{
{[]any{nil}, true, false, ""},
{[]any{"ab"}, false, false, "ab"},
{[]any{1}, false, false, "1"},
{[]any{"one week’s time TEST"}, false, false, "ONE WEEK’S TIME TEST"},
{[]any{"one week's time TEST"}, false, false, "ONE WEEK'S TIME TEST"},
{[]any{"abc测试def"}, false, false, "ABC测试DEF"},
{[]any{"abcテストdef"}, false, false, "ABCテストDEF"},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Upper, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
v, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, v.Kind())
} else {
require.Equal(t, strings.ToUpper(c.res), v.GetString())
}
}
}
_, err := funcs[ast.Upper].getFunction(ctx, []Expression{getVarcharCon()})
require.NoError(t, err)
// Test GBK String
tbl := []struct {
input string
chs string
result string
}{
{"abc", "gbk", "ABC"},
{"一二三", "gbk", "一二三"},
{"àbc", "gbk", "àBC"},
{"àáèéêìíòóùúüāēěīńňōūǎǐǒǔǖǘǚǜⅪⅫ", "gbk", "àáèéêìíòóùúüāēěīńňōūǎǐǒǔǖǘǚǜⅪⅫ"},
{"àáèéêìíòóùúüāēěīńňōūǎǐǒǔǖǘǚǜⅪⅫ", "", "ÀÁÈÉÊÌÍÒÓÙÚÜĀĒĚĪŃŇŌŪǍǏǑǓǕǗǙǛⅪⅫ"},
}
for _, c := range tbl {
err := ctx.GetSessionVars().SetSystemVarWithoutValidation(vardef.CharacterSetConnection, c.chs)
require.NoError(t, err)
f, err := newFunctionForTest(ctx, ast.Upper, primitiveValsToConstants(ctx, []any{c.input})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
require.NoError(t, err)
require.Equal(t, c.result, d.GetString())
}
}
func TestReverse(t *testing.T) {
ctx := createContext(t)
fc := funcs[ast.Reverse]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(nil)))
require.NoError(t, err)
d, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
require.Equal(t, types.KindNull, d.Kind())
tbl := []struct {
Input any
Expect string
}{
{"abc", "cba"},
{"LIKE", "EKIL"},
{123, "321"},
{"", ""},
}
dtbl := tblToDtbl(tbl)
for _, c := range dtbl {
f, err = fc.getFunction(ctx, datumsToConstants(c["Input"]))
require.NoError(t, err)
require.NotNil(t, f)
d, err = evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, c["Expect"][0], d)
}
}
func TestStrcmp(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args []any
isNil bool
getErr bool
res int64
}{
{[]any{"123", "123"}, false, false, 0},
{[]any{"123", "1"}, false, false, 1},
{[]any{"1", "123"}, false, false, -1},
{[]any{"123", "45"}, false, false, -1},
{[]any{123, "123"}, false, false, 0},
{[]any{"12.34", 12.34}, false, false, 0},
{[]any{nil, "123"}, true, false, 0},
{[]any{"123", nil}, true, false, 0},
{[]any{"", "123"}, false, false, -1},
{[]any{"123", ""}, false, false, 1},
{[]any{"", ""}, false, false, 0},
{[]any{"", nil}, true, false, 0},
{[]any{nil, ""}, true, false, 0},
{[]any{nil, nil}, true, false, 0},
{[]any{"123", errors.New("must err")}, false, true, 0},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Strcmp, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetInt64())
}
}
}
}
func TestReplace(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args []any
isNil bool
getErr bool
res string
flen int
}{
{[]any{"www.mysql.com", "mysql", "pingcap"}, false, false, "www.pingcap.com", 17},
{[]any{"www.mysql.com", "w", 1}, false, false, "111.mysql.com", 260},
{[]any{1234, 2, 55}, false, false, "15534", 20},
{[]any{"", "a", "b"}, false, false, "", 0},
{[]any{"abc", "", "d"}, false, false, "abc", 3},
{[]any{"aaa", "a", ""}, false, false, "", 3},
{[]any{nil, "a", "b"}, true, false, "", 0},
{[]any{"a", nil, "b"}, true, false, "", 1},
{[]any{"a", "b", nil}, true, false, "", 1},
{[]any{errors.New("must err"), "a", "b"}, false, true, "", -1},
}
for i, c := range cases {
f, err := newFunctionForTest(ctx, ast.Replace, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
require.Equalf(t, c.flen, f.GetType(ctx).GetFlen(), "test %v", i)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equalf(t, types.KindNull, d.Kind(), "test %v", i)
} else {
require.Equalf(t, c.res, d.GetString(), "test %v", i)
}
}
}
_, err := funcs[ast.Replace].getFunction(ctx, []Expression{NewZero(), NewZero(), NewZero()})
require.NoError(t, err)
}
func TestSubstring(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args []any
isNil bool
getErr bool
res string
}{
{[]any{"Quadratically", 5}, false, false, "ratically"},
{[]any{"Sakila", 1}, false, false, "Sakila"},
{[]any{"Sakila", 2}, false, false, "akila"},
{[]any{"Sakila", -3}, false, false, "ila"},
{[]any{"Sakila", 0}, false, false, ""},
{[]any{"Sakila", 100}, false, false, ""},
{[]any{"Sakila", -100}, false, false, ""},
{[]any{"Quadratically", 5, 6}, false, false, "ratica"},
{[]any{"Sakila", -5, 3}, false, false, "aki"},
{[]any{"Sakila", 2, 0}, false, false, ""},
{[]any{"Sakila", 2, -1}, false, false, ""},
{[]any{"Sakila", 2, 100}, false, false, "akila"},
{[]any{nil, 2, 3}, true, false, ""},
{[]any{"Sakila", nil, 3}, true, false, ""},
{[]any{"Sakila", 2, nil}, true, false, ""},
{[]any{errors.New("must error"), 2, 3}, false, true, ""},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Substring, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetString())
}
}
}
_, err := funcs[ast.Substring].getFunction(ctx, []Expression{NewZero(), NewZero(), NewZero()})
require.NoError(t, err)
_, err = funcs[ast.Substring].getFunction(ctx, []Expression{NewZero(), NewZero()})
require.NoError(t, err)
}
func TestConvert(t *testing.T) {
ctx := createContext(t)
tbl := []struct {
str any
cs string
result string
hasBinaryFlag bool
}{
{"haha", "utf8", "haha", false},
{"haha", "ascii", "haha", false},
{"haha", "binary", "haha", true},
{"haha", "bInAry", "haha", true},
{types.NewBinaryLiteralFromUint(0x7e, -1), "BiNarY", "~", true},
{types.NewBinaryLiteralFromUint(0xe4b8ade696870a, -1), "uTf8", "中文\n", false},
}
for _, v := range tbl {
fc := funcs[ast.Convert]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(v.str, v.cs)))
require.NoError(t, err)
require.NotNil(t, f)
retType := f.getRetTp()
require.Equal(t, strings.ToLower(v.cs), retType.GetCharset())
collate, err := charset.GetDefaultCollation(strings.ToLower(v.cs))
require.NoError(t, err)
require.Equal(t, collate, retType.GetCollate())
require.Equal(t, v.hasBinaryFlag, mysql.HasBinaryFlag(retType.GetFlag()))
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
require.Equal(t, types.KindString, r.Kind())
require.Equal(t, v.result, r.GetString())
}
// Test case for getFunction() error
errTbl := []struct {
str any
cs string
err string
}{
{"haha", "wrongcharset", "[expression:1115]Unknown character set: 'wrongcharset'"},
{"haha", "cp866", "[expression:1115]Unknown character set: 'cp866'"},
}
for _, v := range errTbl {
fc := funcs[ast.Convert]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(v.str, v.cs)))
require.Equal(t, v.err, err.Error())
require.Nil(t, f)
}
// Test wrong charset while evaluating.
fc := funcs[ast.Convert]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums("haha", "utf8")))
require.NoError(t, err)
require.NotNil(t, f)
wrongFunction := f.(*builtinConvertSig)
wrongFunction.tp.SetCharset("wrongcharset")
_, err = evalBuiltinFunc(wrongFunction, ctx, chunk.Row{})
require.Error(t, err)
require.Equal(t, "[expression:1115]Unknown character set: 'wrongcharset'", err.Error())
}
func TestSubstringIndex(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args []any
isNil bool
getErr bool
res string
}{
{[]any{"www.pingcap.com", ".", 2}, false, false, "www.pingcap"},
{[]any{"www.pingcap.com", ".", -2}, false, false, "pingcap.com"},
{[]any{"www.pingcap.com", ".", 0}, false, false, ""},
{[]any{"www.pingcap.com", ".", 100}, false, false, "www.pingcap.com"},
{[]any{"www.pingcap.com", ".", -100}, false, false, "www.pingcap.com"},
{[]any{"www.pingcap.com", "d", 0}, false, false, ""},
{[]any{"www.pingcap.com", "d", 1}, false, false, "www.pingcap.com"},
{[]any{"www.pingcap.com", "d", -1}, false, false, "www.pingcap.com"},
{[]any{"www.pingcap.com", "", 0}, false, false, ""},
{[]any{"www.pingcap.com", "", 1}, false, false, ""},
{[]any{"www.pingcap.com", "", -1}, false, false, ""},
{[]any{"", ".", 0}, false, false, ""},
{[]any{"", ".", 1}, false, false, ""},
{[]any{"", ".", -1}, false, false, ""},
{[]any{nil, ".", 1}, true, false, ""},
{[]any{"www.pingcap.com", nil, 1}, true, false, ""},
{[]any{"www.pingcap.com", ".", nil}, true, false, ""},
{[]any{errors.New("must error"), ".", 1}, false, true, ""},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.SubstringIndex, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetString())
}
}
}
_, err := funcs[ast.SubstringIndex].getFunction(ctx, []Expression{NewZero(), NewZero(), NewZero()})
require.NoError(t, err)
}
func TestSpace(t *testing.T) {
ctx := createContext(t)
stmtCtx := ctx.GetSessionVars().StmtCtx
oldTypeFlags := stmtCtx.TypeFlags()
defer func() {
stmtCtx.SetTypeFlags(oldTypeFlags)
}()
stmtCtx.SetTypeFlags(oldTypeFlags.WithIgnoreTruncateErr(true))
cases := []struct {
arg any
isNil bool
getErr bool
res string
}{
{0, false, false, ""},
{3, false, false, " "},
{mysql.MaxBlobWidth + 1, true, false, ""},
{-1, false, false, ""},
{"abc", false, false, ""},
{"3", false, false, " "},
{1.2, false, false, " "},
{1.9, false, false, " "},
{nil, true, false, ""},
{errors.New("must error"), false, true, ""},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Space, primitiveValsToConstants(ctx, []any{c.arg})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetString())
}
}
}
_, err := funcs[ast.Space].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
}
func TestSpaceSig(t *testing.T) {
ctx := createContext(t)
colTypes := []*types.FieldType{
types.NewFieldType(mysql.TypeLonglong),
}
resultType := &types.FieldType{}
resultType.SetType(mysql.TypeVarchar)
resultType.SetFlen(1000)
args := []Expression{
&Column{Index: 0, RetType: colTypes[0]},
}
base := baseBuiltinFunc{args: args, tp: resultType}
space := &builtinSpaceSig{base, 1000}
input := chunk.NewChunkWithCapacity(colTypes, 10)
input.AppendInt64(0, 6)
input.AppendInt64(0, 1001)
res, isNull, err := space.evalString(ctx, input.GetRow(0))
require.Equal(t, " ", res)
require.False(t, isNull)
require.NoError(t, err)
res, isNull, err = space.evalString(ctx, input.GetRow(1))
require.Equal(t, "", res)
require.True(t, isNull)
require.NoError(t, err)
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Equal(t, 1, len(warnings))
lastWarn := warnings[len(warnings)-1]
require.True(t, terror.ErrorEqual(errWarnAllowedPacketOverflowed, lastWarn.Err))
}
func TestLocate(t *testing.T) {
ctx := createContext(t)
// 1. Test LOCATE without binary input.
tbl := []struct {
Args []any
Want any
}{
{[]any{"bar", "foobarbar"}, 4},
{[]any{"xbar", "foobar"}, 0},
{[]any{"", "foobar"}, 1},
{[]any{"foobar", ""}, 0},
{[]any{"", ""}, 1},
{[]any{"好世", "你好世界"}, 2},
{[]any{"界面", "你好世界"}, 0},
{[]any{"b", "中a英b文"}, 4},
{[]any{"bAr", "foobArbar"}, 4},
{[]any{nil, "foobar"}, nil},
{[]any{"bar", nil}, nil},
{[]any{"bar", "foobarbar", 5}, 7},
{[]any{"xbar", "foobar", 1}, 0},
{[]any{"", "foobar", 2}, 2},
{[]any{"foobar", "", 1}, 0},
{[]any{"", "", 2}, 0},
{[]any{"A", "大A写的A", 0}, 0},
{[]any{"A", "大A写的A", 1}, 2},
{[]any{"A", "大A写的A", 2}, 2},
{[]any{"A", "大A写的A", 3}, 5},
{[]any{"BaR", "foobarBaR", 5}, 7},
{[]any{nil, nil}, nil},
{[]any{"", nil}, nil},
{[]any{nil, ""}, nil},
{[]any{nil, nil, 1}, nil},
{[]any{"", nil, 1}, nil},
{[]any{nil, "", 1}, nil},
{[]any{"foo", nil, -1}, nil},
{[]any{nil, "bar", 0}, nil},
}
Dtbl := tblToDtbl(tbl)
instr := funcs[ast.Locate]
for i, c := range Dtbl {
f, err := instr.getFunction(ctx, datumsToConstants(c["Args"]))
require.NoError(t, err)
got, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
require.NotNil(t, f)
require.Equalf(t, c["Want"][0], got, "[%d]: args: %v", i, c["Args"])
}
// 2. Test LOCATE with binary input
tbl2 := []struct {
Args []any
Want any
}{
{[]any{[]byte("BaR"), "foobArbar"}, 0},
{[]any{"BaR", []byte("foobArbar")}, 0},
{[]any{[]byte("bAr"), "foobarBaR", 5}, 0},
{[]any{"bAr", []byte("foobarBaR"), 5}, 0},
{[]any{"bAr", []byte("foobarbAr"), 5}, 7},
}
Dtbl2 := tblToDtbl(tbl2)
for i, c := range Dtbl2 {
exprs := datumsToConstants(c["Args"])
types.SetBinChsClnFlag(exprs[0].GetType(ctx))
types.SetBinChsClnFlag(exprs[1].GetType(ctx))
f, err := instr.getFunction(ctx, exprs)
require.NoError(t, err)
got, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
require.NotNil(t, f)
require.Equalf(t, c["Want"][0], got, "[%d]: args: %v", i, c["Args"])
}
}
func TestTrim(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args []any
isNil bool
getErr bool
res string
}{
{[]any{" bar "}, false, false, "bar"},
{[]any{"\t bar \n"}, false, false, "\t bar \n"},
{[]any{"\r bar \t"}, false, false, "\r bar \t"},
{[]any{" \tbar\n "}, false, false, "\tbar\n"},
{[]any{""}, false, false, ""},
{[]any{nil}, true, false, ""},
{[]any{"xxxbarxxx", "x"}, false, false, "bar"},
{[]any{"bar", "x"}, false, false, "bar"},
{[]any{" bar ", ""}, false, false, " bar "},
{[]any{"", "x"}, false, false, ""},
{[]any{"bar", nil}, true, false, ""},
{[]any{nil, "x"}, true, false, ""},
{[]any{"xxxbarxxx", "x", int(ast.TrimLeading)}, false, false, "barxxx"},
{[]any{"barxxyz", "xyz", int(ast.TrimTrailing)}, false, false, "barx"},
{[]any{"xxxbarxxx", "x", int(ast.TrimBoth)}, false, false, "bar"},
{[]any{"bar", nil, int(ast.TrimLeading)}, true, false, ""},
{[]any{errors.New("must error")}, false, true, ""},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Trim, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetString())
}
}
}
_, err := funcs[ast.Trim].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
_, err = funcs[ast.Trim].getFunction(ctx, []Expression{NewZero(), NewZero()})
require.NoError(t, err)
_, err = funcs[ast.Trim].getFunction(ctx, []Expression{NewZero(), NewZero(), NewZero()})
require.NoError(t, err)
}
func TestLTrim(t *testing.T) {
ctx := createContext(t)
cases := []struct {
arg any
isNil bool
getErr bool
res string
}{
{" bar ", false, false, "bar "},
{"\t bar ", false, false, "\t bar "},
{" \tbar ", false, false, "\tbar "},
{"\t bar ", false, false, "\t bar "},
{" \tbar ", false, false, "\tbar "},
{"\r bar ", false, false, "\r bar "},
{" \rbar ", false, false, "\rbar "},
{"\n bar ", false, false, "\n bar "},
{" \nbar ", false, false, "\nbar "},
{"bar", false, false, "bar"},
{"", false, false, ""},
{nil, true, false, ""},
{errors.New("must error"), false, true, ""},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.LTrim, primitiveValsToConstants(ctx, []any{c.arg})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetString())
}
}
}
_, err := funcs[ast.LTrim].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
}
func TestRTrim(t *testing.T) {
ctx := createContext(t)
cases := []struct {
arg any
isNil bool
getErr bool
res string
}{
{" bar ", false, false, " bar"},
{"bar", false, false, "bar"},
{"bar \n", false, false, "bar \n"},
{"bar\n ", false, false, "bar\n"},
{"bar \r", false, false, "bar \r"},
{"bar\r ", false, false, "bar\r"},
{"bar \t", false, false, "bar \t"},
{"bar\t ", false, false, "bar\t"},
{"", false, false, ""},
{nil, true, false, ""},
{errors.New("must error"), false, true, ""},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.RTrim, primitiveValsToConstants(ctx, []any{c.arg})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetString())
}
}
}
_, err := funcs[ast.RTrim].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
}
func TestHexFunc(t *testing.T) {
ctx := createContext(t)
cases := []struct {
arg any
isNil bool
getErr bool
res string
}{
{"abc", false, false, "616263"},
{"你好", false, false, "E4BDA0E5A5BD"},
{12, false, false, "C"},
{12.3, false, false, "C"},
{12.8, false, false, "D"},
{-1, false, false, "FFFFFFFFFFFFFFFF"},
{-12.3, false, false, "FFFFFFFFFFFFFFF4"},
{-12.8, false, false, "FFFFFFFFFFFFFFF3"},
{types.NewBinaryLiteralFromUint(0xC, -1), false, false, "0C"},
{0x12, false, false, "12"},
{nil, true, false, ""},
{errors.New("must err"), false, true, ""},
{"🀁", false, false, "F09F8081"},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Hex, primitiveValsToConstants(ctx, []any{c.arg})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetString())
}
}
}
strCases := []struct {
arg string
chs string
res string
errCode int
}{
{"你好", "", "E4BDA0E5A5BD", 0},
{"你好", "gbk", "C4E3BAC3", 0},
{"一忒(๑•ㅂ•)و✧", "", "E4B880E5BF9228E0B991E280A2E38582E280A229D988E29CA7", 0},
{"一忒(๑•ㅂ•)و✧", "gbk", "", errno.ErrInvalidCharacterString},
{"🀁", "gb18030", "9438E131", 0},
}
for _, c := range strCases {
err := ctx.GetSessionVars().SetSystemVarWithoutValidation(vardef.CharacterSetConnection, c.chs)
require.NoError(t, err)
f, err := newFunctionForTest(ctx, ast.Hex, primitiveValsToConstants(ctx, []any{c.arg})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.errCode != 0 {
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), strconv.Itoa(c.errCode)))
} else {
require.NoError(t, err)
require.Equal(t, c.res, d.GetString())
}
}
_, err := funcs[ast.Hex].getFunction(ctx, []Expression{getInt8Con()})
require.NoError(t, err)
_, err = funcs[ast.Hex].getFunction(ctx, []Expression{getVarcharCon()})
require.NoError(t, err)
}
func TestUnhexFunc(t *testing.T) {
ctx := createContext(t)
cases := []struct {
arg any
isNil bool
getErr bool
res string
}{
{"4D7953514C", false, false, "MySQL"},
{"1267", false, false, string([]byte{0x12, 0x67})},
{"126", false, false, string([]byte{0x01, 0x26})},
{"", false, false, ""},
{1267, false, false, string([]byte{0x12, 0x67})},
{126, false, false, string([]byte{0x01, 0x26})},
{1267.3, true, false, ""},
{"string", true, false, ""},
{"你好", true, false, ""},
{nil, true, false, ""},
{errors.New("must error"), false, true, ""},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Unhex, primitiveValsToConstants(ctx, []any{c.arg})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetString())
}
}
}
_, err := funcs[ast.Unhex].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
}
func TestBitLength(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args any
chs string
expected int64
isNil bool
getErr bool
}{
{"hi", "", 16, false, false},
{"你好", "", 48, false, false},
{"", "", 0, false, false},
{"abc", "gbk", 24, false, false},
{"一二三", "gbk", 48, false, false},
{"一二三", "", 72, false, false},
{"一二三!", "gbk", 56, false, false},
{"一二三!", "", 80, false, false},
}
for _, c := range cases {
err := ctx.GetSessionVars().SetSystemVarWithoutValidation(vardef.CharacterSetConnection, c.chs)
require.NoError(t, err)
f, err := newFunctionForTest(ctx, ast.BitLength, primitiveValsToConstants(ctx, []any{c.args})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.expected, d.GetInt64())
}
}
}
_, err := funcs[ast.BitLength].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
}
func TestChar(t *testing.T) {
ctx := createContext(t)
typeFlags := ctx.GetSessionVars().StmtCtx.TypeFlags()
ctx.GetSessionVars().StmtCtx.SetTypeFlags(typeFlags.WithIgnoreTruncateErr(true))
tbl := []struct {
str string
iNum int64
fNum float64
charset any
result any
warnings int
}{
{"65", 66, 67.5, "utf8", "ABD", 0}, // float
{"65", 16740, 67.5, "utf8", "AAdD", 0}, // large num
{"65", -1, 67.5, nil, "A\xff\xff\xff\xffD", 0}, // negative int
{"a", -1, 67.5, nil, "\x00\xff\xff\xff\xffD", 0}, // invalid 'a'
{"65", -1, 67.5, "utf8", nil, 1}, // with utf8, return nil
{"a", -1, 67.5, "utf8", nil, 1}, // with utf8, return nil
{"1234567", 1234567, 1234567, "gbk", "\u0012謬\u0012謬\u0012謬", 0}, // test char for gbk
{"123456789", 123456789, 123456789, "gbk", nil, 1}, // invalid 123456789 in gbk
}
run := func(i int, result any, warnCnt int, dts ...any) {
fc := funcs[ast.CharFunc]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(dts...)))
require.NoError(t, err, i)
require.NotNil(t, f, i)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err, i)
testutil.DatumEqual(t, types.NewDatum(result), r, i)
if warnCnt != 0 {
warnings := ctx.GetSessionVars().StmtCtx.TruncateWarnings(0)
require.Equal(t, warnCnt, len(warnings), fmt.Sprintf("%d: %v", i, warnings))
}
}
for i, v := range tbl {
run(i, v.result, v.warnings, v.str, v.iNum, v.fNum, v.charset)
}
// char() returns null only when the sql_mode is strict.
require.True(t, ctx.GetSessionVars().SQLMode.HasStrictMode())
run(-1, nil, 1, 123456, "utf8")
ctx.GetSessionVars().SQLMode = ctx.GetSessionVars().SQLMode &^ (mysql.ModeStrictTransTables | mysql.ModeStrictAllTables)
require.False(t, ctx.GetSessionVars().SQLMode.HasStrictMode())
run(-2, string([]byte{1}), 1, 123456, "utf8")
}
func TestCharLength(t *testing.T) {
ctx := createContext(t)
tbl := []struct {
input any
result any
}{
{"33", 2}, // string
{"你好", 2}, // mb string
{33, 2}, // int
{3.14, 4}, // float
{nil, nil}, // nil
}
for _, v := range tbl {
fc := funcs[ast.CharLength]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(v.input)))
require.NoError(t, err)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(v.result), r)
}
// Test binary string
tbl = []struct {
input any
result any
}{
{"33", 2}, // string
{"你好", 6}, // mb string
{"CAFÉ", 5}, // mb string
{"", 0}, // mb string
{nil, nil}, // nil
}
for _, v := range tbl {
fc := funcs[ast.CharLength]
arg := datumsToConstants(types.MakeDatums(v.input))
tp := arg[0].GetType(ctx)
tp.SetType(mysql.TypeVarString)
tp.SetCharset(charset.CharsetBin)
tp.SetCollate(charset.CollationBin)
tp.SetFlen(types.UnspecifiedLength)
tp.SetFlag(mysql.BinaryFlag)
f, err := fc.getFunction(ctx, arg)
require.NoError(t, err)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(v.result), r)
}
}
func TestFindInSet(t *testing.T) {
ctx := createContext(t)
for _, c := range []struct {
str any
strlst any
ret any
}{
{"foo", "foo,bar", 1},
{"foo", "foobar,bar", 0},
{" foo ", "foo, foo ", 2},
{"", "foo,bar,", 3},
{"", "", 0},
{1, 1, 1},
{1, "1", 1},
{"1", 1, 1},
{"a,b", "a,b,c", 0},
{"foo", nil, nil},
{nil, "bar", nil},
} {
fc := funcs[ast.FindInSet]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(c.str, c.strlst)))
require.NoError(t, err)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(c.ret), r, fmt.Sprintf("FindInSet(%s, %s)", c.str, c.strlst))
}
}
func TestField(t *testing.T) {
ctx := createContext(t)
stmtCtx := ctx.GetSessionVars().StmtCtx
oldTypeFlags := stmtCtx.TypeFlags()
defer func() {
stmtCtx.SetTypeFlags(oldTypeFlags)
}()
stmtCtx.SetTypeFlags(oldTypeFlags.WithIgnoreTruncateErr(true))
tbl := []struct {
argLst []any
ret any
}{
{[]any{"ej", "Hej", "ej", "Heja", "hej", "foo"}, int64(2)},
{[]any{"fo", "Hej", "ej", "Heja", "hej", "foo"}, int64(0)},
{[]any{"ej", "Hej", "ej", "Heja", "ej", "hej", "foo"}, int64(2)},
{[]any{1, 2, 3, 11, 1}, int64(4)},
{[]any{nil, 2, 3, 11, 1}, int64(0)},
{[]any{1.1, 2.1, 3.1, 11.1, 1.1}, int64(4)},
{[]any{1.1, "2.1", "3.1", "11.1", "1.1"}, int64(4)},
{[]any{"1.1a", 2.1, 3.1, 11.1, 1.1}, int64(4)},
{[]any{1.10, 0, 11e-1}, int64(2)},
{[]any{"abc", 0, 1, 11.1, 1.1}, int64(1)},
}
for _, c := range tbl {
fc := funcs[ast.Field]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(c.argLst...)))
require.NoError(t, err)
require.NotNil(t, f)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(c.ret), r)
}
}
func TestLpad(t *testing.T) {
ctx := createContext(t)
tests := []struct {
str string
len int64
padStr string
expect any
}{
{"hi", 5, "?", "???hi"},
{"hi", 1, "?", "h"},
{"hi", 0, "?", ""},
{"hi", -1, "?", nil},
{"hi", 1, "", "h"},
{"hi", 5, "", ""},
{"hi", 5, "ab", "abahi"},
{"hi", 6, "ab", "ababhi"},
{"中文", 5, "字符", "字符字中文"},
{"中文", 1, "a", "中"},
{"中文", -5, "字符", nil},
{"中文", 10, "", ""},
}
fc := funcs[ast.Lpad]
for _, test := range tests {
str := types.NewStringDatum(test.str)
length := types.NewIntDatum(test.len)
padStr := types.NewStringDatum(test.padStr)
f, err := fc.getFunction(ctx, datumsToConstants([]types.Datum{str, length, padStr}))
require.NoError(t, err)
require.NotNil(t, f)
result, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
if test.expect == nil {
require.Equal(t, types.KindNull, result.Kind())
} else {
expect, _ := test.expect.(string)
require.Equal(t, expect, result.GetString())
}
}
}
func TestRpad(t *testing.T) {
ctx := createContext(t)
tests := []struct {
str string
len int64
padStr string
expect any
}{
{"hi", 5, "?", "hi???"},
{"hi", 1, "?", "h"},
{"hi", 0, "?", ""},
{"hi", -1, "?", nil},
{"hi", 1, "", "h"},
{"hi", 5, "", ""},
{"hi", 5, "ab", "hiaba"},
{"hi", 6, "ab", "hiabab"},
{"中文", 5, "字符", "中文字符字"},
{"中文", 1, "a", "中"},
{"中文", -5, "字符", nil},
{"中文", 10, "", ""},
}
fc := funcs[ast.Rpad]
for _, test := range tests {
str := types.NewStringDatum(test.str)
length := types.NewIntDatum(test.len)
padStr := types.NewStringDatum(test.padStr)
f, err := fc.getFunction(ctx, datumsToConstants([]types.Datum{str, length, padStr}))
require.NoError(t, err)
require.NotNil(t, f)
result, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
if test.expect == nil {
require.Equal(t, types.KindNull, result.Kind())
} else {
expect, _ := test.expect.(string)
require.Equal(t, expect, result.GetString())
}
}
}
func TestRpadSig(t *testing.T) {
ctx := createContext(t)
colTypes := []*types.FieldType{
types.NewFieldType(mysql.TypeVarchar),
types.NewFieldType(mysql.TypeLonglong),
types.NewFieldType(mysql.TypeVarchar),
}
resultType := &types.FieldType{}
resultType.SetType(mysql.TypeVarchar)
resultType.SetFlen(1000)
args := []Expression{
&Column{Index: 0, RetType: colTypes[0]},
&Column{Index: 1, RetType: colTypes[1]},
&Column{Index: 2, RetType: colTypes[2]},
}
base := baseBuiltinFunc{args: args, tp: resultType}
rpad := &builtinRpadUTF8Sig{base, 1000}
input := chunk.NewChunkWithCapacity(colTypes, 10)
input.AppendString(0, "abc")
input.AppendString(0, "abc")
input.AppendInt64(1, 6)
input.AppendInt64(1, 10000)
input.AppendString(2, "123")
input.AppendString(2, "123")
res, isNull, err := rpad.evalString(ctx, input.GetRow(0))
require.Equal(t, "abc123", res)
require.False(t, isNull)
require.NoError(t, err)
res, isNull, err = rpad.evalString(ctx, input.GetRow(1))
require.Equal(t, "", res)
require.True(t, isNull)
require.NoError(t, err)
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Equal(t, 1, len(warnings))
lastWarn := warnings[len(warnings)-1]
require.Truef(t, terror.ErrorEqual(errWarnAllowedPacketOverflowed, lastWarn.Err), "err %v", lastWarn.Err)
}
func TestInsertBinarySig(t *testing.T) {
ctx := createContext(t)
colTypes := []*types.FieldType{
types.NewFieldType(mysql.TypeVarchar),
types.NewFieldType(mysql.TypeLonglong),
types.NewFieldType(mysql.TypeLonglong),
types.NewFieldType(mysql.TypeVarchar),
}
resultType := &types.FieldType{}
resultType.SetType(mysql.TypeVarchar)
resultType.SetFlen(3)
args := []Expression{
&Column{Index: 0, RetType: colTypes[0]},
&Column{Index: 1, RetType: colTypes[1]},
&Column{Index: 2, RetType: colTypes[2]},
&Column{Index: 3, RetType: colTypes[3]},
}
base := baseBuiltinFunc{args: args, tp: resultType}
insert := &builtinInsertSig{base, 3}
input := chunk.NewChunkWithCapacity(colTypes, 2)
input.AppendString(0, "abc")
input.AppendString(0, "abc")
input.AppendString(0, "abc")
input.AppendNull(0)
input.AppendString(0, "abc")
input.AppendString(0, "abc")
input.AppendString(0, "abc")
input.AppendInt64(1, 3)
input.AppendInt64(1, 3)
input.AppendInt64(1, 0)
input.AppendInt64(1, 3)
input.AppendNull(1)
input.AppendInt64(1, 3)
input.AppendInt64(1, 3)
input.AppendInt64(2, -1)
input.AppendInt64(2, -1)
input.AppendInt64(2, -1)
input.AppendInt64(2, -1)
input.AppendInt64(2, -1)
input.AppendNull(2)
input.AppendInt64(2, -1)
input.AppendString(3, "d")
input.AppendString(3, "de")
input.AppendString(3, "d")
input.AppendString(3, "d")
input.AppendString(3, "d")
input.AppendString(3, "d")
input.AppendNull(3)
res, isNull, err := insert.evalString(ctx, input.GetRow(0))
require.Equal(t, "abd", res)
require.False(t, isNull)
require.NoError(t, err)
res, isNull, err = insert.evalString(ctx, input.GetRow(1))
require.Equal(t, "", res)
require.True(t, isNull)
require.NoError(t, err)
res, isNull, err = insert.evalString(ctx, input.GetRow(2))
require.Equal(t, "abc", res)
require.False(t, isNull)
require.NoError(t, err)
res, isNull, err = insert.evalString(ctx, input.GetRow(3))
require.Equal(t, "", res)
require.True(t, isNull)
require.NoError(t, err)
res, isNull, err = insert.evalString(ctx, input.GetRow(4))
require.Equal(t, "", res)
require.True(t, isNull)
require.NoError(t, err)
res, isNull, err = insert.evalString(ctx, input.GetRow(5))
require.Equal(t, "", res)
require.True(t, isNull)
require.NoError(t, err)
res, isNull, err = insert.evalString(ctx, input.GetRow(6))
require.Equal(t, "", res)
require.True(t, isNull)
require.NoError(t, err)
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Equal(t, 1, len(warnings))
lastWarn := warnings[len(warnings)-1]
require.Truef(t, terror.ErrorEqual(errWarnAllowedPacketOverflowed, lastWarn.Err), "err %v", lastWarn.Err)
}
func TestInstr(t *testing.T) {
ctx := createContext(t)
tbl := []struct {
Args []any
Want any
}{
{[]any{"foobarbar", "bar"}, 4},
{[]any{"xbar", "foobar"}, 0},
{[]any{123456234, 234}, 2},
{[]any{123456, 567}, 0},
{[]any{1e10, 1e2}, 1},
{[]any{1.234, ".234"}, 2},
{[]any{1.234, ""}, 1},
{[]any{"", 123}, 0},
{[]any{"", ""}, 1},
{[]any{"中文美好", "美好"}, 3},
{[]any{"中文美好", "世界"}, 0},
{[]any{"中文abc", "a"}, 3},
{[]any{"live long and prosper", "long"}, 6},
{[]any{"not binary string", "binary"}, 5},
{[]any{"upper case", "upper"}, 1},
{[]any{"UPPER CASE", "CASE"}, 7},
{[]any{"中文abc", "abc"}, 3},
{[]any{"foobar", nil}, nil},
{[]any{nil, "foobar"}, nil},
{[]any{nil, nil}, nil},
}
Dtbl := tblToDtbl(tbl)
instr := funcs[ast.Instr]
for i, c := range Dtbl {
f, err := instr.getFunction(ctx, datumsToConstants(c["Args"]))
require.NoError(t, err)
require.NotNil(t, f)
got, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
require.Equalf(t, c["Want"][0], got, "[%d]: args: %v", i, c["Args"])
}
}
func TestLoadFile(t *testing.T) {
ctx := createContext(t)
cases := []struct {
arg any
isNil bool
getErr bool
res string
}{
{"", true, false, ""},
{"/tmp/tikv/tikv.frm", true, false, ""},
{"tidb.sql", true, false, ""},
{nil, true, false, ""},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.LoadFile, primitiveValsToConstants(ctx, []any{c.arg})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetString())
}
}
}
_, err := funcs[ast.LoadFile].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
}
func TestMakeSet(t *testing.T) {
ctx := createContext(t)
tbl := []struct {
argList []any
ret any
}{
{[]any{1, "a", "b", "c"}, "a"},
{[]any{1 | 4, "hello", "nice", "world"}, "hello,world"},
{[]any{1 | 4, "hello", "nice", nil, "world"}, "hello"},
{[]any{0, "a", "b", "c"}, ""},
{[]any{nil, "a", "b", "c"}, nil},
{[]any{-100 | 4, "hello", "nice", "abc", "world"}, "abc,world"},
{[]any{-1, "hello", "nice", "abc", "world"}, "hello,nice,abc,world"},
}
for _, c := range tbl {
fc := funcs[ast.MakeSet]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(c.argList...)))
require.NoError(t, err)
require.NotNil(t, f)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(c.ret), r)
}
}
func TestOct(t *testing.T) {
ctx := createContext(t)
octTests := []struct {
origin any
ret string
}{
{"-2.7", "1777777777777777777776"},
{-1.5, "1777777777777777777777"},
{-1, "1777777777777777777777"},
{"0", "0"},
{"1", "1"},
{"8", "10"},
{"12", "14"},
{"20", "24"},
{"100", "144"},
{"1024", "2000"},
{"2048", "4000"},
{1.0, "1"},
{9.5, "11"},
{13, "15"},
{1025, "2001"},
{"8a8", "10"},
{"abc", "0"},
// overflow uint64
{"9999999999999999999999999", "1777777777777777777777"},
{"-9999999999999999999999999", "1777777777777777777777"},
{types.NewBinaryLiteralFromUint(255, -1), "377"}, // b'11111111'
{types.NewBinaryLiteralFromUint(10, -1), "12"}, // b'1010'
{types.NewBinaryLiteralFromUint(5, -1), "5"}, // b'0101'
}
fc := funcs[ast.Oct]
for _, tt := range octTests {
in := types.NewDatum(tt.origin)
f, _ := fc.getFunction(ctx, datumsToConstants([]types.Datum{in}))
require.NotNil(t, f)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
res, err := r.ToString()
require.NoError(t, err)
require.Equalf(t, tt.ret, res, "select oct(%v);", tt.origin)
}
// tt NULL input for sha
var argNull types.Datum
f, _ := fc.getFunction(ctx, datumsToConstants([]types.Datum{argNull}))
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
require.True(t, r.IsNull())
}
func TestFormat(t *testing.T) {
ctx := createContext(t)
formatTests := []struct {
number any
precision any
locale string
ret any
}{
{12332.12341111111111111111111111111111111111111, 4, "en_US", "12,332.1234"},
{nil, 22, "en_US", nil},
}
formatTests1 := []struct {
number any
precision any
ret any
warnings int
}{
// issue #8796
{1.12345, 4, "1.1235", 0},
{9.99999, 4, "10.0000", 0},
{1.99999, 4, "2.0000", 0},
{1.09999, 4, "1.1000", 0},
{-2.5000, 0, "-3", 0},
{12332.123444, 4, "12,332.1234", 0},
{12332.123444, 0, "12,332", 0},
{12332.123444, -4, "12,332", 0},
{-12332.123444, 4, "-12,332.1234", 0},
{-12332.123444, 0, "-12,332", 0},
{-12332.123444, -4, "-12,332", 0},
{"12332.123444", "4", "12,332.1234", 0},
{"12332.123444A", "4", "12,332.1234", 1},
{"-12332.123444", "4", "-12,332.1234", 0},
{"-12332.123444A", "4", "-12,332.1234", 1},
{"A123345", "4", "0.0000", 1},
{"-A123345", "4", "0.0000", 1},
{"-12332.123444", "A", "-12,332", 1},
{"12332.123444", "A", "12,332", 1},
{"-12332.123444", "4A", "-12,332.1234", 1},
{"12332.123444", "4A", "12,332.1234", 1},
{"-A12332.123444", "A", "0", 2},
{"A12332.123444", "A", "0", 2},
{"-A12332.123444", "4A", "0.0000", 2},
{"A12332.123444", "4A", "0.0000", 2},
{"-.12332.123444", "4A", "-0.1233", 2},
{".12332.123444", "4A", "0.1233", 2},
{"12332.1234567890123456789012345678901", 22, "12,332.1234567890110000000000", 0},
{nil, 22, nil, 0},
{1, 1024, "1.000000000000000000000000000000", 0},
{"", 1, "0.0", 0},
{1, "", "1", 1},
}
formatTests2 := struct {
number any
precision any
locale string
ret any
}{-12332.123456, -4, "zh_CN", "-12,332"}
formatTests3 := struct {
number any
precision any
locale string
ret any
}{"-12332.123456", "4", "de_GE", "-12,332.1235"}
formatTests4 := struct {
number any
precision any
locale any
ret any
}{1, 4, nil, "1.0000"}
fc := funcs[ast.Format]
for _, tt := range formatTests {
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(tt.number, tt.precision, tt.locale)))
require.NoError(t, err)
require.NotNil(t, f)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(tt.ret), r)
}
origTypeFlags := ctx.GetSessionVars().StmtCtx.TypeFlags()
ctx.GetSessionVars().StmtCtx.SetTypeFlags(origTypeFlags.WithTruncateAsWarning(true))
for _, tt := range formatTests1 {
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(tt.number, tt.precision)))
require.NoError(t, err)
require.NotNil(t, f)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(tt.ret), r, fmt.Sprintf("test %v", tt))
if tt.warnings > 0 {
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Lenf(t, warnings, tt.warnings, "test %v", tt)
for i := range tt.warnings {
require.Truef(t, terror.ErrorEqual(types.ErrTruncatedWrongVal, warnings[i].Err), "test %v", tt)
}
ctx.GetSessionVars().StmtCtx.SetWarnings([]contextutil.SQLWarn{})
}
}
ctx.GetSessionVars().StmtCtx.SetTypeFlags(origTypeFlags)
f2, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(formatTests2.number, formatTests2.precision, formatTests2.locale)))
require.NoError(t, err)
require.NotNil(t, f2)
r2, err := evalBuiltinFunc(f2, ctx, chunk.Row{})
testutil.DatumEqual(t, types.NewDatum(errors.New("not implemented")), types.NewDatum(err))
testutil.DatumEqual(t, types.NewDatum(formatTests2.ret), r2)
f3, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(formatTests3.number, formatTests3.precision, formatTests3.locale)))
require.NoError(t, err)
require.NotNil(t, f3)
r3, err := evalBuiltinFunc(f3, ctx, chunk.Row{})
testutil.DatumEqual(t, types.NewDatum(errors.New("not support for the specific locale")), types.NewDatum(err))
testutil.DatumEqual(t, types.NewDatum(formatTests3.ret), r3)
f4, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(formatTests4.number, formatTests4.precision, formatTests4.locale)))
require.NoError(t, err)
require.NotNil(t, f4)
r4, err := evalBuiltinFunc(f4, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(formatTests4.ret), r4)
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Equal(t, 2, len(warnings))
for i := range 2 {
require.True(t, terror.ErrorEqual(errUnknownLocale, warnings[i].Err))
}
ctx.GetSessionVars().StmtCtx.SetWarnings([]contextutil.SQLWarn{})
}
func TestFromBase64(t *testing.T) {
ctx := createContext(t)
tests := []struct {
args any
expect any
}{
{"", ""},
{"YWJj", "abc"},
{"YWIgYw==", "ab c"},
{"YWIKYw==", "ab\nc"},
{"YWIJYw==", "ab\tc"},
{"cXdlcnR5MTIzNDU2", "qwerty123456"},
{
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0\nNTY3ODkrL0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4\neXowMTIzNDU2Nzg5Ky9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3Bx\ncnN0dXZ3eHl6MDEyMzQ1Njc4OSsv",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
},
{
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw==",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
},
{
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw==",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
},
{
"QUJDREVGR0hJSkt\tMTU5PUFFSU1RVVld\nYWVphYmNkZ\rWZnaGlqa2xt bm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw==",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
},
}
fc := funcs[ast.FromBase64]
for _, test := range tests {
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(test.args)))
require.NoError(t, err)
require.NotNil(t, f)
result, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
if test.expect == nil {
require.Equal(t, types.KindNull, result.Kind())
} else {
expect, _ := test.expect.(string)
require.Equal(t, expect, result.GetString())
}
}
}
func TestFromBase64Sig(t *testing.T) {
ctx := createContext(t)
colTypes := []*types.FieldType{
types.NewFieldType(mysql.TypeVarchar),
}
tests := []struct {
args string
expect string
isNil bool
maxAllowPacket uint64
}{
{"YWJj", "abc", false, 3},
{"YWJj", "", true, 2},
{
"QUJDREVGR0hJSkt\tMTU5PUFFSU1RVVld\nYWVphYmNkZ\rWZnaGlqa2xt bm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw==",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
false,
70,
},
{
"QUJDREVGR0hJSkt\tMTU5PUFFSU1RVVld\nYWVphYmNkZ\rWZnaGlqa2xt bm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw==",
"",
true,
69,
},
}
args := []Expression{
&Column{Index: 0, RetType: colTypes[0]},
}
for _, test := range tests {
resultType := &types.FieldType{}
resultType.SetType(mysql.TypeVarchar)
resultType.SetFlen(mysql.MaxBlobWidth)
base := baseBuiltinFunc{args: args, tp: resultType}
fromBase64 := &builtinFromBase64Sig{base, test.maxAllowPacket}
input := chunk.NewChunkWithCapacity(colTypes, 1)
input.AppendString(0, test.args)
res, isNull, err := fromBase64.evalString(ctx, input.GetRow(0))
require.NoError(t, err)
require.Equal(t, test.isNil, isNull)
if isNull {
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Equal(t, 1, len(warnings))
lastWarn := warnings[len(warnings)-1]
require.True(t, terror.ErrorEqual(errWarnAllowedPacketOverflowed, lastWarn.Err))
ctx.GetSessionVars().StmtCtx.SetWarnings([]contextutil.SQLWarn{})
}
require.Equal(t, test.expect, res)
}
}
func TestInsert(t *testing.T) {
ctx := createContext(t)
tests := []struct {
args []any
expect any
}{
{[]any{"Quadratic", 3, 4, "What"}, "QuWhattic"},
{[]any{"Quadratic", -1, 4, "What"}, "Quadratic"},
{[]any{"Quadratic", 3, 100, "What"}, "QuWhat"},
{[]any{nil, 3, 100, "What"}, nil},
{[]any{"Quadratic", nil, 4, "What"}, nil},
{[]any{"Quadratic", 3, nil, "What"}, nil},
{[]any{"Quadratic", 3, 4, nil}, nil},
{[]any{"Quadratic", 3, -1, "What"}, "QuWhat"},
{[]any{"Quadratic", 3, 1, "What"}, "QuWhatdratic"},
{[]any{"Quadratic", -1, nil, "What"}, nil},
{[]any{"Quadratic", -1, 4, nil}, nil},
{[]any{"我叫小雨呀", 3, 2, "王雨叶"}, "我叫王雨叶呀"},
{[]any{"我叫小雨呀", -1, 2, "王雨叶"}, "我叫小雨呀"},
{[]any{"我叫小雨呀", 3, 100, "王雨叶"}, "我叫王雨叶"},
{[]any{nil, 3, 100, "王雨叶"}, nil},
{[]any{"我叫小雨呀", nil, 4, "王雨叶"}, nil},
{[]any{"我叫小雨呀", 3, nil, "王雨叶"}, nil},
{[]any{"我叫小雨呀", 3, 4, nil}, nil},
{[]any{"我叫小雨呀", 3, -1, "王雨叶"}, "我叫王雨叶"},
{[]any{"我叫小雨呀", 3, 1, "王雨叶"}, "我叫王雨叶雨呀"},
{[]any{"我叫小雨呀", -1, nil, "王雨叶"}, nil},
{[]any{"我叫小雨呀", -1, 2, nil}, nil},
}
fc := funcs[ast.InsertFunc]
for _, test := range tests {
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(test.args...)))
require.NoError(t, err)
require.NotNil(t, f)
result, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
if test.expect == nil {
require.Equal(t, types.KindNull, result.Kind())
} else {
expect, _ := test.expect.(string)
require.Equal(t, expect, result.GetString())
}
}
}
func TestOrd(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args any
expected int64
chs string
isNil bool
getErr bool
}{
{"2", 50, "", false, false},
{2, 50, "", false, false},
{"23", 50, "", false, false},
{23, 50, "", false, false},
{2.3, 50, "", false, false},
{nil, 0, "", true, false},
{"", 0, "", false, false},
{"你好", 14990752, "utf8mb4", false, false},
{"にほん", 14909867, "utf8mb4", false, false},
{"한국", 15570332, "utf8mb4", false, false},
{"👍", 4036989325, "utf8mb4", false, false},
{"א", 55184, "utf8mb4", false, false},
{"abc", 97, "gbk", false, false},
{"一二三", 53947, "gbk", false, false},
{"àáèé", 43172, "gbk", false, false},
{"数据库", 51965, "gbk", false, false},
}
for _, c := range cases {
err := ctx.GetSessionVars().SetSystemVarWithoutValidation(vardef.CharacterSetConnection, c.chs)
require.NoError(t, err)
f, err := newFunctionForTest(ctx, ast.Ord, primitiveValsToConstants(ctx, []any{c.args})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.expected, d.GetInt64())
}
}
}
_, err := funcs[ast.Ord].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
}
func TestElt(t *testing.T) {
ctx := createContext(t)
tbl := []struct {
argLst []any
ret any
}{
{[]any{1, "Hej", "ej", "Heja", "hej", "foo"}, "Hej"},
{[]any{9, "Hej", "ej", "Heja", "hej", "foo"}, nil},
{[]any{-1, "Hej", "ej", "Heja", "ej", "hej", "foo"}, nil},
{[]any{0, 2, 3, 11, 1}, nil},
{[]any{3, 2, 3, 11, 1}, "11"},
{[]any{1.1, "2.1", "3.1", "11.1", "1.1"}, "2.1"},
}
for _, c := range tbl {
fc := funcs[ast.Elt]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(c.argLst...)))
require.NoError(t, err)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(c.ret), r)
}
}
func TestExportSet(t *testing.T) {
ctx := createContext(t)
estd := []struct {
argLst []any
res string
}{
{[]any{-9223372036854775807, "Y", "N", ",", 5}, "Y,N,N,N,N"},
{[]any{-6, "Y", "N", ",", 5}, "N,Y,N,Y,Y"},
{[]any{5, "Y", "N", ",", 4}, "Y,N,Y,N"},
{[]any{5, "Y", "N", ",", 0}, ""},
{[]any{5, "Y", "N", ",", 1}, "Y"},
{[]any{6, "1", "0", ",", 10}, "0,1,1,0,0,0,0,0,0,0"},
{[]any{333333, "Ysss", "sN", "---", 9}, "Ysss---sN---Ysss---sN---Ysss---sN---sN---sN---sN"},
{[]any{7, "Y", "N"}, "Y,Y,Y,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N"},
{[]any{7, "Y", "N", 6}, "Y6Y6Y6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N"},
{[]any{7, "Y", "N", 6, 133}, "Y6Y6Y6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N6N"},
}
fc := funcs[ast.ExportSet]
for _, c := range estd {
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(c.argLst...)))
require.NoError(t, err)
require.NotNil(t, f)
exportSetRes, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
res, err := exportSetRes.ToString()
require.NoError(t, err)
require.Equal(t, c.res, res)
}
}
func TestBin(t *testing.T) {
tbl := []struct {
Input any
Expected any
}{
{"10", "1010"},
{"10.2", "1010"},
{"10aa", "1010"},
{"10.2aa", "1010"},
{"aaa", "0"},
{"", nil},
{10, "1010"},
{10.0, "1010"},
{-1, "1111111111111111111111111111111111111111111111111111111111111111"},
{"-1", "1111111111111111111111111111111111111111111111111111111111111111"},
{nil, nil},
}
fc := funcs[ast.Bin]
dtbl := tblToDtbl(tbl)
ctx := mock.NewContext()
typeFlags := ctx.GetSessionVars().StmtCtx.TypeFlags()
ctx.GetSessionVars().StmtCtx.SetTypeFlags(typeFlags.WithIgnoreTruncateErr(true))
for _, c := range dtbl {
f, err := fc.getFunction(ctx, datumsToConstants(c["Input"]))
require.NoError(t, err)
require.NotNil(t, f)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(c["Expected"][0]), r)
}
}
func TestQuote(t *testing.T) {
ctx := createContext(t)
tbl := []struct {
arg any
ret any
}{
{`Don\'t!`, `'Don\\\'t!'`},
{`Don't`, `'Don\'t'`},
{`Don"`, `'Don"'`},
{`Don\"`, `'Don\\"'`},
{`\'`, `'\\\''`},
{`\"`, `'\\"'`},
{`萌萌哒(๑•ᴗ•๑)😊`, `'萌萌哒(๑•ᴗ•๑)😊'`},
{`㍿㌍㍑㌫`, `'㍿㌍㍑㌫'`},
{string([]byte{0, 26}), `'\0\Z'`},
{nil, "NULL"},
}
for _, c := range tbl {
fc := funcs[ast.Quote]
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(c.arg)))
require.NoError(t, err)
require.NotNil(t, f)
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
testutil.DatumEqual(t, types.NewDatum(c.ret), r)
}
}
func TestToBase64(t *testing.T) {
ctx := createContext(t)
tests := []struct {
args any
expect string
isNil bool
getErr bool
}{
{"", "", false, false},
{"abc", "YWJj", false, false},
{"ab c", "YWIgYw==", false, false},
{1, "MQ==", false, false},
{1.1, "MS4x", false, false},
{"ab\nc", "YWIKYw==", false, false},
{"ab\tc", "YWIJYw==", false, false},
{"qwerty123456", "cXdlcnR5MTIzNDU2", false, false},
{
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0\nNTY3ODkrLw==",
false,
false,
},
{
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0\nNTY3ODkrL0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4\neXowMTIzNDU2Nzg5Ky9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3Bx\ncnN0dXZ3eHl6MDEyMzQ1Njc4OSsv",
false,
false,
},
{
"ABCD EFGHI\nJKLMNOPQRSTUVWXY\tZabcdefghijklmnopqrstuv wxyz012\r3456789+/",
"QUJDRCAgRUZHSEkKSktMTU5PUFFSU1RVVldYWQlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1diAgd3h5\nejAxMg0zNDU2Nzg5Ky8=",
false,
false,
},
{nil, "", true, false},
}
if strconv.IntSize == 32 {
tests = append(tests, struct {
args any
expect string
isNil bool
getErr bool
}{
strings.Repeat("a", 1589695687),
"",
true,
false,
})
}
for _, test := range tests {
f, err := newFunctionForTest(ctx, ast.ToBase64, primitiveValsToConstants(ctx, []any{test.args})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if test.getErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if test.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, test.expect, d.GetString())
}
}
}
_, err := funcs[ast.ToBase64].getFunction(ctx, []Expression{NewZero()})
require.NoError(t, err)
// Test GBK String
tbl := []struct {
input string
chs string
result string
}{
{"abc", "gbk", "YWJj"},
{"一二三", "gbk", "0ru2/sj9"},
{"一二三", "", "5LiA5LqM5LiJ"},
{"一二三!", "gbk", "0ru2/sj9IQ=="},
{"一二三!", "", "5LiA5LqM5LiJIQ=="},
}
for _, c := range tbl {
err := ctx.GetSessionVars().SetSystemVarWithoutValidation(vardef.CharacterSetConnection, c.chs)
require.NoError(t, err)
f, err := newFunctionForTest(ctx, ast.ToBase64, primitiveValsToConstants(ctx, []any{c.input})...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
require.NoError(t, err)
require.Equal(t, c.result, d.GetString())
}
}
func TestToBase64Sig(t *testing.T) {
ctx := createContext(t)
colTypes := []*types.FieldType{
types.NewFieldType(mysql.TypeVarchar),
}
tests := []struct {
args string
expect string
isNil bool
maxAllowPacket uint64
}{
{"abc", "YWJj", false, 4},
{"abc", "", true, 3},
{
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0\nNTY3ODkrLw==",
false,
89,
},
{
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
"",
true,
88,
},
{
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0\nNTY3ODkrL0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4\neXowMTIzNDU2Nzg5Ky9BQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3Bx\ncnN0dXZ3eHl6MDEyMzQ1Njc4OSsv",
false,
259,
},
{
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
"",
true,
258,
},
}
args := []Expression{
&Column{Index: 0, RetType: colTypes[0]},
}
for _, test := range tests {
resultType := &types.FieldType{}
resultType.SetType(mysql.TypeVarchar)
resultType.SetFlen(base64NeededEncodedLength(len(test.args)))
base := baseBuiltinFunc{args: args, tp: resultType}
toBase64 := &builtinToBase64Sig{base, test.maxAllowPacket}
input := chunk.NewChunkWithCapacity(colTypes, 1)
input.AppendString(0, test.args)
res, isNull, err := toBase64.evalString(ctx, input.GetRow(0))
require.NoError(t, err)
if test.isNil {
require.True(t, isNull)
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Equal(t, 1, len(warnings))
lastWarn := warnings[len(warnings)-1]
require.True(t, terror.ErrorEqual(errWarnAllowedPacketOverflowed, lastWarn.Err))
ctx.GetSessionVars().StmtCtx.SetWarnings([]contextutil.SQLWarn{})
} else {
require.False(t, isNull)
}
require.Equal(t, test.expect, res)
}
}
func TestStringRight(t *testing.T) {
ctx := createContext(t)
fc := funcs[ast.Right]
tests := []struct {
str any
length any
expect any
}{
{"helloworld", 5, "world"},
{"helloworld", 10, "helloworld"},
{"helloworld", 11, "helloworld"},
{"helloworld", -1, ""},
{"", 2, ""},
{nil, 2, nil},
}
for _, test := range tests {
str := types.NewDatum(test.str)
length := types.NewDatum(test.length)
f, _ := fc.getFunction(ctx, datumsToConstants([]types.Datum{str, length}))
result, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
if result.IsNull() {
require.Nil(t, test.expect)
continue
}
res, err := result.ToString()
require.NoError(t, err)
require.Equal(t, test.expect, res)
}
}
func TestWeightString(t *testing.T) {
ctx := createContext(t)
fc := funcs[ast.WeightString]
tests := []struct {
expr any
padding string
length int
expect any
}{
{nil, "NONE", 0, nil},
{7, "NONE", 0, nil},
{7.0, "NONE", 0, nil},
{"a", "NONE", 0, "a"},
{"a ", "NONE", 0, "a"},
{"中", "NONE", 0, "中"},
{"中 ", "NONE", 0, "中"},
{nil, "CHAR", 5, nil},
{7, "CHAR", 5, nil},
{7.0, "NONE", 0, nil},
{"a", "CHAR", 5, "a"},
{"a ", "CHAR", 5, "a"},
{"中", "CHAR", 5, "中"},
{"中 ", "CHAR", 5, "中"},
{nil, "BINARY", 5, nil},
{7, "BINARY", 2, "7\x00"},
{7.0, "NONE", 0, nil},
{"a", "BINARY", 1, "a"},
{"ab", "BINARY", 1, "a"},
{"a", "BINARY", 5, "a\x00\x00\x00\x00"},
{"a ", "BINARY", 5, "a \x00\x00\x00"},
{"中", "BINARY", 1, "\xe4"},
{"中", "BINARY", 2, "\xe4\xb8"},
{"中", "BINARY", 3, "中"},
{"中", "BINARY", 5, "中\x00\x00"},
}
for _, test := range tests {
str := types.NewDatum(test.expr)
var f builtinFunc
var err error
if test.padding == "NONE" {
f, err = fc.getFunction(ctx, datumsToConstants([]types.Datum{str}))
} else {
padding := types.NewDatum(test.padding)
length := types.NewDatum(test.length)
f, err = fc.getFunction(ctx, datumsToConstants([]types.Datum{str, padding, length}))
}
require.NoError(t, err)
retType := f.getRetTp()
require.Equal(t, charset.CollationBin, retType.GetCollate())
// Reset warnings.
ctx.GetSessionVars().StmtCtx.ResetForRetry()
result, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
if result.IsNull() {
require.Nil(t, test.expect)
continue
}
res, err := result.ToString()
require.NoError(t, err)
require.Equal(t, test.expect, res)
if test.expr == nil {
continue
}
strExpr := fmt.Sprintf("%v", test.expr)
if test.padding == "BINARY" && test.length < len(strExpr) {
expectWarn := fmt.Sprintf("[expression:1292]Truncated incorrect BINARY(%d) value: '%s'", test.length, strExpr)
obtainedWarns := ctx.GetSessionVars().StmtCtx.GetWarnings()
require.Equal(t, 1, len(obtainedWarns))
require.Equal(t, "Warning", obtainedWarns[0].Level)
require.Equal(t, expectWarn, obtainedWarns[0].Err.Error())
}
}
}
func TestTranslate(t *testing.T) {
ctx := createContext(t)
cases := []struct {
args []any
isNil bool
isErr bool
res string
}{
{[]any{"ABC", "A", "B"}, false, false, "BBC"},
{[]any{"ABC", "Z", "ABC"}, false, false, "ABC"},
{[]any{"A.B.C", ".A", "|"}, false, false, "|B|C"},
{[]any{"中文", "文", "国"}, false, false, "中国"},
{[]any{"UPPERCASE", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"}, false, false, "uppercase"},
{[]any{"lowercase", "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"}, false, false, "LOWERCASE"},
{[]any{"aaaaabbbbb", "aaabbb", "xyzXYZ"}, false, false, "xxxxxXXXXX"},
{[]any{"Ti*DB User's Guide", " */'", "___"}, false, false, "Ti_DB_Users_Guide"},
{[]any{"abc", "ab", ""}, false, false, "c"},
{[]any{"aaa", "a", ""}, false, false, ""},
{[]any{"", "null", "null"}, false, false, ""},
{[]any{"null", "", "null"}, false, false, "null"},
{[]any{"null", "null", ""}, false, false, ""},
{[]any{nil, "error", "error"}, true, false, ""},
{[]any{"error", nil, "error"}, true, false, ""},
{[]any{"error", "error", nil}, true, false, ""},
{[]any{nil, nil, nil}, true, false, ""},
{[]any{[]byte{255}, []byte{255}, []byte{255}}, false, false, string([]byte{255})},
{[]any{[]byte{255, 255}, []byte{255}, []byte{254}}, false, false, string([]byte{254, 254})},
{[]any{[]byte{255, 255}, []byte{255, 255}, []byte{254, 253}}, false, false, string([]byte{254, 254})},
{[]any{[]byte{255, 254, 253, 252, 251}, []byte{253, 252, 251}, []byte{254, 253}}, false, false, string([]byte{255, 254, 254, 253})},
}
for _, c := range cases {
f, err := newFunctionForTest(ctx, ast.Translate, primitiveValsToConstants(ctx, c.args)...)
require.NoError(t, err)
d, err := f.Eval(ctx, chunk.Row{})
if c.isErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if c.isNil {
require.Equal(t, types.KindNull, d.Kind())
} else {
require.Equal(t, c.res, d.GetString())
}
}
}
}
func TestCIWeightString(t *testing.T) {
ctx := createContext(t)
type weightStringTest struct {
str string
padding string
length int
expect any
}
checkResult := func(collation string, tests []weightStringTest) {
fc := funcs[ast.WeightString]
for _, test := range tests {
str := types.NewCollationStringDatum(test.str, collation)
var f builtinFunc
var err error
if test.padding == "NONE" {
f, err = fc.getFunction(ctx, datumsToConstants([]types.Datum{str}))
} else {
padding := types.NewDatum(test.padding)
length := types.NewDatum(test.length)
f, err = fc.getFunction(ctx, datumsToConstants([]types.Datum{str, padding, length}))
}
require.NoError(t, err)
result, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err)
if result.IsNull() {
require.Nil(t, test.expect)
continue
}
res, err := result.ToString()
require.NoError(t, err)
require.Equal(t, test.expect, res, "test case: '%s' '%s' %d", test.str, test.padding, test.length)
}
}
generalTests := []weightStringTest{
{"aAÁàãăâ", "NONE", 0, "\x00A\x00A\x00A\x00A\x00A\x00A\x00A"},
{"中", "NONE", 0, "\x4E\x2D"},
{"a", "CHAR", 5, "\x00A"},
{"a ", "CHAR", 5, "\x00A"},
{"中", "CHAR", 5, "\x4E\x2D"},
{"中 ", "CHAR", 5, "\x4E\x2D"},
{"a", "BINARY", 1, "a"},
{"ab", "BINARY", 1, "a"},
{"a", "BINARY", 5, "a\x00\x00\x00\x00"},
{"a ", "BINARY", 5, "a \x00\x00\x00"},
{"中", "BINARY", 1, "\xe4"},
{"中", "BINARY", 2, "\xe4\xb8"},
{"中", "BINARY", 3, "中"},
{"中", "BINARY", 5, "中\x00\x00"},
}
unicodeTests := []weightStringTest{
{"aAÁàãăâ", "NONE", 0, "\x0e3\x0e3\x0e3\x0e3\x0e3\x0e3\x0e3"},
{"中", "NONE", 0, "\xfb\x40\xce\x2d"},
{"a", "CHAR", 5, "\x0e3"},
{"a ", "CHAR", 5, "\x0e3"},
{"中", "CHAR", 5, "\xfb\x40\xce\x2d"},
{"中 ", "CHAR", 5, "\xfb\x40\xce\x2d"},
{"a", "BINARY", 1, "a"},
{"ab", "BINARY", 1, "a"},
{"a", "BINARY", 5, "a\x00\x00\x00\x00"},
{"a ", "BINARY", 5, "a \x00\x00\x00"},
{"中", "BINARY", 1, "\xe4"},
{"中", "BINARY", 2, "\xe4\xb8"},
{"中", "BINARY", 3, "中"},
{"中", "BINARY", 5, "中\x00\x00"},
}
unicode0900Tests := []weightStringTest{
{"aAÁàãăâ", "NONE", 0, "\x1cG\x1cG\x1cG\x1cG\x1cG\x1cG\x1cG"},
{"中", "NONE", 0, "\xfb\x40\xce\x2d"},
{"a", "CHAR", 5, "\x1c\x47\x02\x09\x02\x09\x02\x09\x02\x09"},
{"a ", "CHAR", 5, "\x1c\x47\x02\x09\x02\x09\x02\x09\x02\x09"},
{"中", "CHAR", 5, "\xfb\x40\xce\x2d\x02\x09\x02\x09\x02\x09\x02\x09"},
{"中 ", "CHAR", 5, "\xfb\x40\xce\x2d\x02\x09\x02\x09\x02\x09\x02\x09"},
{"a", "BINARY", 1, "a"},
{"ab", "BINARY", 1, "a"},
{"a", "BINARY", 5, "a\x00\x00\x00\x00"},
{"a ", "BINARY", 5, "a \x00\x00\x00"},
{"中", "BINARY", 1, "\xe4"},
{"中", "BINARY", 2, "\xe4\xb8"},
{"中", "BINARY", 3, "中"},
{"中", "BINARY", 5, "中\x00\x00"},
}
checkResult("utf8mb4_general_ci", generalTests)
checkResult("utf8mb4_unicode_ci", unicodeTests)
checkResult("utf8mb4_0900_ai_ci", unicode0900Tests)
}
// TestFormatWithLocale tests the 3-argument version of FORMAT(X, D, locale)
// with various locales and number formats.
func TestFormatWithLocale(t *testing.T) {
ctx := createContext(t)
fc := funcs[ast.Format]
tests := []struct {
number any
precision any
locale any // Use 'any' to test NULL locale
ret any // Expected result
warning bool // True if we expect an 'Unknown locale' warning
desc string
}{
// --- Style: CommaDot (123,456.78) ---
// This is the default fallback for most unhandled locales.
{1234567.89, 2, "en_US", "1,234,567.89", false, "CommaDot (en_US) - standard"},
{-98765.432, 2, "zh_CN", "-98,765.43", false, "CommaDot (zh_CN) - negative, rounding"},
{0.01, 4, "ja_JP", "0.0100", false, "CommaDot (ja_JP) - decimal padding"},
{12345, 0, "en_GB", "12,345", false, "CommaDot (en_GB) - no decimal part"},
{1.2, 2, "ko_KR", "1.20", false, "CommaDot (ko_KR) - extra locale"},
{500.5, 1, "th_TH", "500.5", false, "CommaDot (th_TH) - extra locale"},
{7777, 0, "en_AU", "7,777", false, "CommaDot (en_AU) - extra locale"},
{-88.88, 2, "zh_TW", "-88.88", false, "CommaDot (zh_TW) - extra locale"},
// Fallback locales that MySQL treats as en_US
{9876543.21, 1, "es_MX", "9,876,543.2", false, "CommaDot (es_MX) - MySQL fallback"},
{3000.14, 2, "ce_RU", "3,000.14", false, "CommaDot (ce_RU) - MySQL fallback"},
{4000.1, 1, "ky_KG", "4,000.1", false, "CommaDot (ky_KG) - MySQL fallback"},
{200, 2, "aa_DJ", "200.00", false, "CommaDot (aa_DJ) - MySQL fallback"},
{7890123.456, 2, "ps_AF", "7,890,123.46", false, "CommaDot (ps_AF) - MySQL fallback"},
{12345.67, 2, "an_ES", "12,345.67", false, "CommaDot (an_ES) - MySQL fallback"},
{12345.67, 2, "az_AZ", "12,345.67", false, "CommaDot (az_AZ) - MySQL fallback"},
{12345.67, 2, "br_FR", "12,345.67", false, "CommaDot (br_FR) - MySQL fallback"},
{3000.14, 2, "kv_RU", "3,000.14", false, "CommaDot (kv_RU) - MySQL fallback"},
{12345.67, 3, "su_ID", "12,345.670", false, "CommaDot (su_ID) - MySQL fallback"},
// --- Style: DotComma (123.456,78) ---
{7654321.98, 2, "de_DE", "7.654.321,98", false, "DotComma (de_DE) - large number"},
{-9.999, 2, "es_ES", "-10,00", false, "DotComma (es_ES) - negative, rounding up to 10"},
{"-123.45", 1, "id_ID", "-123,5", false, "DotComma (id_ID) - string input"},
{99, 1, "vi_VN", "99,0", false, "DotComma (vi_VN) - extra locale"},
{8888.8, 0, "ro_RO", "8.889", false, "DotComma (ro_RO) - extra locale, rounding"},
{1234.567, 2, "da_DK", "1.234,57", false, "DotComma (da_DK) - extra locale, rounding"},
{555.55, 1, "tr_TR", "555,6", false, "DotComma (tr_TR) - extra locale, rounding"},
{1234.56, 2, "nb_NO", "1.234,56", false, "DotComma (nb_NO) - MySQL behavior"},
{1234.56, 2, "uk_UA", "1.234,56", false, "DotComma (uk_UA) - MySQL behavior"},
{12345.67, 3, "no_NO", "12.345,670", false, "DotComma (no_NO) - MySQL behavior"},
// --- Style: SpaceComma (123 456,78) ---
{-0.88, 1, "ru_RU", "-0,9", false, "SpaceComma (ru_RU) - negative, rounding"},
{98765, 0, "sv_SE", "98 765", false, "SpaceComma (sv_SE) - no decimal part"},
{2000, 2, "cs_CZ", "2 000,00", false, "SpaceComma (cs_CZ) - extra locale, padding"},
// --- Style: NoneComma (123456,78) ---
{-2.23, 1, "el_GR", "-2,2", false, "NoneComma (el_GR) - negative, rounding"},
{44.44, 1, "pt_PT", "44,4", false, "NoneComma (pt_PT) - extra locale"},
{12345, 0, "it_IT", "12345", false, "NoneComma (it_IT) - MySQL behavior"},
{100.5, 3, "pt_BR", "100,500", false, "NoneComma (pt_BR) - MySQL behavior"},
{500000.1, 2, "fr_FR", "500000,10", false, "NoneComma (fr_FR) - MySQL behavior"},
{1999.9, 0, "pl_PL", "2000", false, "NoneComma (pl_PL) - MySQL behavior"},
{123, 2, "fr_CH", "123,00", false, "NoneComma (fr_CH) - MySQL behavior"},
{12345, 0, "de_AT", "12345", false, "NoneComma (de_AT) - MySQL behavior"},
{1000000, 2, "bg_BG", "1000000,00", false, "NoneComma (bg_BG) - MySQL behavior"},
// --- Style: AposDot (123'456.78) ---
{4567890.123, 2, "de_CH", "4'567'890.12", false, "AposDot (de_CH) - large number"},
// --- Style: AposComma (123'456,78) ---
{4567890.123, 2, "it_CH", "4'567'890,12", false, "AposComma (it_CH) - MySQL behavior"},
// --- Style: NoneDot (123456.78) ---
{1000000.5, 0, "ar_SA", "1000001", false, "NoneDot (ar_SA) - no grouping, rounding"},
{12345.6, 1, "sr_RS", "12345.6", false, "NoneDot (sr_RS) - MySQL behavior"},
// --- Style: Indian (1,23,45,67,890.123) ---
{1234567890.123, 3, "en_IN", "1,23,45,67,890.123", false, "Indian (en_IN) - lakh/crore grouping"},
{987654321, 0, "ta_IN", "98,76,54,321", false, "Indian (ta_IN) - no decimal"},
{-5000.5, 1, "te_IN", "-5,000.5", false, "Indian (te_IN) - only one separator"},
// --- Special Cases (Case, NULL, Invalid) ---
{12345.67, 2, "dE_dE", "12.345,67", false, "DotComma (de_DE) - case insensitive"},
{12345.67, 2, "en_us", "12,345.67", false, "CommaDot (en_US) - case insensitive"},
// Test NULL locale: should fallback to en_US and produce a warning
{12345.67, 2, nil, "12,345.67", true, "NULL locale fallback"},
// Test an invalid/unmapped locale
// Should fallback to en_US (styleCommaDot) and issue a warning.
{12345.67, 2, "de_GE", "12,345.67", true, "Invalid locale 'de_GE' fallback"},
{12345.67, 2, "non_existent", "12,345.67", true, "Invalid locale 'non_existent' fallback"},
}
for _, tt := range tests {
// Clear warnings for each test run
ctx.GetSessionVars().StmtCtx.SetWarnings(nil)
// Get function signature
f, err := fc.getFunction(ctx, datumsToConstants(types.MakeDatums(tt.number, tt.precision, tt.locale)))
require.NoError(t, err, "test: %s", tt.desc)
require.NotNil(t, f, "test: %s", tt.desc)
// Evaluate
r, err := evalBuiltinFunc(f, ctx, chunk.Row{})
require.NoError(t, err, "test: %s", tt.desc)
// Check result
testutil.DatumEqual(t, types.NewDatum(tt.ret), r, "test: %s", tt.desc)
// Check warnings
warnings := ctx.GetSessionVars().StmtCtx.GetWarnings()
if tt.warning {
require.Len(t, warnings, 1, "test: %s", tt.desc)
// Check if it's the 'Unknown locale' warning
require.True(t, terror.ErrorEqual(errUnknownLocale, warnings[0].Err), "test: %s", tt.desc)
} else {
require.Len(t, warnings, 0, "test: %s", tt.desc)
}
}
}