diff --git a/go/vt/vtgate/evalengine/cached_size.go b/go/vt/vtgate/evalengine/cached_size.go index 4290ae032ee..9e0b091b5c0 100644 --- a/go/vt/vtgate/evalengine/cached_size.go +++ b/go/vt/vtgate/evalengine/cached_size.go @@ -1423,6 +1423,18 @@ func (cached *builtinRepeat) CachedSize(alloc bool) int64 { size += cached.CallExpr.CachedSize(false) return size } +func (cached *builtinReverse) CachedSize(alloc bool) int64 { + if cached == nil { + return int64(0) + } + size := int64(0) + if alloc { + size += int64(48) + } + // field CallExpr vitess.io/vitess/go/vt/vtgate/evalengine.CallExpr + size += cached.CallExpr.CachedSize(false) + return size +} func (cached *builtinRound) CachedSize(alloc bool) int64 { if cached == nil { return int64(0) @@ -1495,6 +1507,18 @@ func (cached *builtinSin) CachedSize(alloc bool) int64 { size += cached.CallExpr.CachedSize(false) return size } +func (cached *builtinSpace) CachedSize(alloc bool) int64 { + if cached == nil { + return int64(0) + } + size := int64(0) + if alloc { + size += int64(48) + } + // field CallExpr vitess.io/vitess/go/vt/vtgate/evalengine.CallExpr + size += cached.CallExpr.CachedSize(false) + return size +} func (cached *builtinSqrt) CachedSize(alloc bool) int64 { if cached == nil { return int64(0) diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index 868ec6322b9..39e02d07d55 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -1430,6 +1430,29 @@ func (asm *assembler) Fn_ASCII() { }, "FN ASCII VARCHAR(SP-1)") } +func (asm *assembler) Fn_REVERSE() { + asm.emit(func(env *ExpressionEnv) int { + arg := env.vm.stack[env.vm.sp-1].(*evalBytes) + + arg.tt = int16(sqltypes.VarChar) + arg.bytes = reverse(arg) + return 1 + }, "FN REVERSE VARCHAR(SP-1)") +} + +func (asm *assembler) Fn_SPACE(col collations.TypedCollation) { + asm.emit(func(env *ExpressionEnv) int { + arg := env.vm.stack[env.vm.sp-1].(*evalInt64).i + + if !validMaxLength(1, arg) { + env.vm.stack[env.vm.sp-1] = nil + return 1 + } + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalText(space(arg), col) + return 1 + }, "FN SPACE INT64(SP-1)") +} + func (asm *assembler) Fn_ORD(col collations.ID) { asm.emit(func(env *ExpressionEnv) int { arg := env.vm.stack[env.vm.sp-1].(*evalBytes) diff --git a/go/vt/vtgate/evalengine/fn_string.go b/go/vt/vtgate/evalengine/fn_string.go index f69b8db1e72..a6ab0a1c1cd 100644 --- a/go/vt/vtgate/evalengine/fn_string.go +++ b/go/vt/vtgate/evalengine/fn_string.go @@ -47,6 +47,16 @@ type ( CallExpr } + builtinReverse struct { + CallExpr + collate collations.ID + } + + builtinSpace struct { + CallExpr + collate collations.ID + } + builtinOrd struct { CallExpr collate collations.ID @@ -100,6 +110,8 @@ var _ IR = (*builtinChangeCase)(nil) var _ IR = (*builtinCharLength)(nil) var _ IR = (*builtinLength)(nil) var _ IR = (*builtinASCII)(nil) +var _ IR = (*builtinReverse)(nil) +var _ IR = (*builtinSpace)(nil) var _ IR = (*builtinOrd)(nil) var _ IR = (*builtinBitLength)(nil) var _ IR = (*builtinCollation)(nil) @@ -253,6 +265,100 @@ func (call *builtinASCII) compile(c *compiler) (ctype, error) { return ctype{Type: sqltypes.Int64, Col: collationNumeric, Flag: nullableFlags(str.Flag)}, nil } +func reverse(in *evalBytes) []byte { + cs := colldata.Lookup(in.col.Collation).Charset() + b := in.bytes + + out, end := make([]byte, len(b)), len(b) + for len(b) > 0 { + _, size := cs.DecodeRune(b) + copy(out[end-size:end], b[:size]) + b = b[size:] + end -= size + } + return out +} + +func (call *builtinReverse) eval(env *ExpressionEnv) (eval, error) { + arg, err := call.arg1(env) + if err != nil { + return nil, err + } + if arg == nil { + return nil, nil + } + + b, ok := arg.(*evalBytes) + if !ok { + b, err = evalToVarchar(arg, call.collate, true) + if err != nil { + return nil, err + } + } + + return newEvalText(reverse(b), b.col), nil +} + +func (call *builtinReverse) compile(c *compiler) (ctype, error) { + arg, err := call.Arguments[0].compile(c) + if err != nil { + return ctype{}, err + } + + skip := c.compileNullCheck1(arg) + + switch { + case arg.isTextual(): + default: + c.asm.Convert_xc(1, sqltypes.VarChar, c.collation, 0, false) + } + + c.asm.Fn_REVERSE() + c.asm.jumpDestination(skip) + return ctype{Type: sqltypes.VarChar, Col: arg.Col, Flag: flagNullable}, nil +} + +func space(num int64) []byte { + num = max(num, 0) + + spaces := bytes.Repeat([]byte{0x20}, int(num)) + return spaces +} + +func (call *builtinSpace) eval(env *ExpressionEnv) (eval, error) { + arg, err := call.arg1(env) + if err != nil { + return nil, err + } + if arg == nil { + return nil, nil + } + + num := evalToInt64(arg).i + + if !validMaxLength(1, num) { + return nil, nil + } + col := typedCoercionCollation(sqltypes.VarChar, call.collate) + return newEvalText(space(num), col), nil +} + +func (call *builtinSpace) compile(c *compiler) (ctype, error) { + arg, err := call.Arguments[0].compile(c) + if err != nil { + return ctype{}, err + } + + skip := c.compileNullCheck1(arg) + + _ = c.compileToInt64(arg, 1) + + col := typedCoercionCollation(sqltypes.VarChar, call.collate) + c.asm.Fn_SPACE(col) + c.asm.jumpDestination(skip) + return ctype{Type: sqltypes.VarChar, Col: col, Flag: flagNullable}, nil +} + func charOrd(b []byte, coll collations.ID) int64 { if len(b) == 0 { return 0 diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index 77bd534ef2d..45cfdc4dd10 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -69,6 +69,8 @@ var Cases = []TestCase{ {Run: FnLength}, {Run: FnBitLength}, {Run: FnAscii}, + {Run: FnReverse}, + {Run: FnSpace}, {Run: FnOrd}, {Run: FnRepeat}, {Run: FnLeft}, @@ -1352,6 +1354,34 @@ func FnAscii(yield Query) { } } +func FnReverse(yield Query) { + for _, str := range inputStrings { + yield(fmt.Sprintf("REVERSE(%s)", str), nil) + } +} + +func FnSpace(yield Query) { + counts := []string{ + "0", + "12", + "23", + "-1", + "-12393128120", + "-432766734237843674326423876243876234786", + "'-432766734237843674326423876243876234786'", + "432766734237843674326423876243876234786", + "1073741825", + "1.5", + "-3.2", + "'jhgjhg'", + "6", + } + + for _, c := range counts { + yield(fmt.Sprintf("SPACE(%s)", c), nil) + } +} + func FnOrd(yield Query) { for _, str := range inputStrings { yield(fmt.Sprintf("ORD(%s)", str), nil) diff --git a/go/vt/vtgate/evalengine/translate_builtin.go b/go/vt/vtgate/evalengine/translate_builtin.go index 8f3bd49b8c7..8b0b8326baa 100644 --- a/go/vt/vtgate/evalengine/translate_builtin.go +++ b/go/vt/vtgate/evalengine/translate_builtin.go @@ -295,6 +295,16 @@ func (ast *astCompiler) translateFuncExpr(fn *sqlparser.FuncExpr) (IR, error) { return nil, argError(method) } return &builtinASCII{CallExpr: call}, nil + case "reverse": + if len(args) != 1 { + return nil, argError(method) + } + return &builtinReverse{CallExpr: call, collate: ast.cfg.Collation}, nil + case "space": + if len(args) != 1 { + return nil, argError(method) + } + return &builtinSpace{CallExpr: call, collate: ast.cfg.Collation}, nil case "ord": if len(args) != 1 { return nil, argError(method)