Skip to content

Commit

Permalink
evalengine: Add UUID functions (#13097)
Browse files Browse the repository at this point in the history
This adds `BIN_TO_UUID`, `IS_UUID`, `UUID` & `UUID_TO_BIN`. This doesn't
implement `UUID_SHORT` though since that has specific requirements to
use the `server_id` which is not something that conceptually makes sense
for `vtgate` since it works across different backend MySQL servers.

So we avoid adding that here since there's no clear path for how to
implement that.

Signed-off-by: Dirkjan Bussink <d.bussink@gmail.com>
  • Loading branch information
dbussink authored May 17, 2023
1 parent e05a0b8 commit 5989efb
Show file tree
Hide file tree
Showing 8 changed files with 560 additions and 30 deletions.
48 changes: 48 additions & 0 deletions go/vt/vtgate/evalengine/cached_size.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

118 changes: 118 additions & 0 deletions go/vt/vtgate/evalengine/compiler_asm.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import (
"strconv"
"time"

"github.com/google/uuid"

"vitess.io/vitess/go/hack"
"vitess.io/vitess/go/mysql/collations"
"vitess.io/vitess/go/mysql/collations/charset"
Expand Down Expand Up @@ -4122,3 +4124,119 @@ func (asm *assembler) Fn_CONCAT_WS(tt querypb.Type, tc collations.TypedCollation
return 1
}, "FN CONCAT_WS VARCHAR(SP-1) VARCHAR(SP-2)...VARCHAR(SP-N)")
}

func (asm *assembler) Fn_BIN_TO_UUID0(col collations.TypedCollation) {
asm.emit(func(env *ExpressionEnv) int {
arg := env.vm.stack[env.vm.sp-1].(*evalBytes)

parsed, err := uuid.FromBytes(arg.bytes)
if err != nil {
env.vm.stack[env.vm.sp-1] = nil
env.vm.err = errIncorrectUUID(arg.bytes, "bin_to_uuid")
return 1
}
arg.bytes = hack.StringBytes(parsed.String())
arg.tt = int16(sqltypes.VarChar)
arg.col = col
return 1
}, "FN BIN_TO_UUID VARBINARY(SP-1)")
}

func (asm *assembler) Fn_BIN_TO_UUID1(col collations.TypedCollation) {
asm.adjustStack(-1)
asm.emit(func(env *ExpressionEnv) int {
arg := env.vm.stack[env.vm.sp-2].(*evalBytes)
b := arg.bytes

if env.vm.stack[env.vm.sp-1] != nil &&
env.vm.stack[env.vm.sp-1].(*evalInt64).i != 0 {
b = swapUUIDFrom(b)
}

parsed, err := uuid.FromBytes(b)
if err != nil {
env.vm.stack[env.vm.sp-2] = nil
env.vm.err = errIncorrectUUID(arg.bytes, "bin_to_uuid")
env.vm.sp--
return 1
}
arg.bytes = hack.StringBytes(parsed.String())
arg.tt = int16(sqltypes.VarChar)
arg.col = col
env.vm.sp--
return 1
}, "FN BIN_TO_UUID VARBINARY(SP-2) INT64(SP-1)")
}

func (asm *assembler) Fn_IS_UUID() {
asm.emit(func(env *ExpressionEnv) int {
arg := env.vm.stack[env.vm.sp-1].(*evalBytes)

_, err := uuid.ParseBytes(arg.bytes)
env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalBool(err == nil)
return 1
}, "FN IS_UUID VARBINARY(SP-1)")
}

func (asm *assembler) Fn_UUID() {
asm.adjustStack(1)
asm.emit(func(env *ExpressionEnv) int {
v, err := uuid.NewUUID()
if err != nil {
env.vm.err = err
env.vm.sp++
return 1
}
m, err := v.MarshalText()
if err != nil {
env.vm.err = err
env.vm.sp++
return 1
}

env.vm.stack[env.vm.sp] = env.vm.arena.newEvalText(m, collationUtf8mb3)
env.vm.sp++
return 1
}, "FN UUID")
}

func (asm *assembler) Fn_UUID_TO_BIN0() {
asm.emit(func(env *ExpressionEnv) int {
arg := env.vm.stack[env.vm.sp-1].(*evalBytes)

parsed, err := uuid.ParseBytes(arg.bytes)
if err != nil {
env.vm.stack[env.vm.sp-1] = nil
env.vm.err = errIncorrectUUID(arg.bytes, "uuid_to_bin")
return 1
}
arg.bytes = parsed[:]
arg.tt = int16(sqltypes.VarBinary)
arg.col = collationBinary
return 1
}, "FN UUID_TO_BIN VARBINARY(SP-1)")
}

func (asm *assembler) Fn_UUID_TO_BIN1() {
asm.adjustStack(-1)
asm.emit(func(env *ExpressionEnv) int {
arg := env.vm.stack[env.vm.sp-2].(*evalBytes)
parsed, err := uuid.ParseBytes(arg.bytes)
if err != nil {
env.vm.stack[env.vm.sp-2] = nil
env.vm.err = errIncorrectUUID(arg.bytes, "uuid_to_bin")
env.vm.sp--
return 1
}
b := parsed[:]
if env.vm.stack[env.vm.sp-1] != nil &&
env.vm.stack[env.vm.sp-1].(*evalInt64).i != 0 {
b = swapUUIDTo(b)
}
arg.bytes = b
arg.tt = int16(sqltypes.VarBinary)
arg.col = collationBinary
env.vm.sp--
return 1
}, "FN UUID_TO_BIN VARBINARY(SP-2) INT64(SP-1)")
}
36 changes: 36 additions & 0 deletions go/vt/vtgate/evalengine/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package evalengine
import (
"fmt"
"strconv"
"unicode/utf8"

"vitess.io/vitess/go/hack"
"vitess.io/vitess/go/mysql/collations"
Expand Down Expand Up @@ -361,3 +362,38 @@ func valueToEval(value sqltypes.Value, collation collations.TypedCollation) (eva
return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Type is not supported: %q %s", value, value.Type())
}
}

const hexchars = "0123456789ABCDEF"

func sanitizeErrorValue(s []byte) []byte {
var buf []byte
for width := 0; len(s) > 0; s = s[width:] {
r := rune(s[0])
width = 1
if r >= utf8.RuneSelf {
r, width = utf8.DecodeLastRune(s)
}
if width == 1 && r == utf8.RuneError {
buf = append(buf, `\x`...)
buf = append(buf, hexchars[s[0]>>4])
buf = append(buf, hexchars[s[0]&0xF])
continue
}

if strconv.IsPrint(r) {
if r < utf8.RuneSelf {
buf = append(buf, byte(r))
} else {
b := [utf8.UTFMax]byte{}
n := utf8.EncodeRune(b[:], r)
buf = append(buf, b[:n]...)
}
continue
}

buf = append(buf, `\x`...)
buf = append(buf, hexchars[s[0]>>4])
buf = append(buf, hexchars[s[0]&0xF])
}
return buf
}
30 changes: 0 additions & 30 deletions go/vt/vtgate/evalengine/eval_temporal.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package evalengine

import (
"time"
"unicode/utf8"

"vitess.io/vitess/go/hack"
"vitess.io/vitess/go/mysql/datetime"
Expand Down Expand Up @@ -156,35 +155,6 @@ func newEvalTime(time datetime.Time, l int) *evalTemporal {
return &evalTemporal{t: sqltypes.Time, dt: datetime.DateTime{Time: time.Round(l)}, prec: uint8(l)}
}

func sanitizeErrorValue(s []byte) []byte {
b := make([]byte, 0, len(s)+1)
invalid := false // previous byte was from an invalid UTF-8 sequence
for i := 0; i < len(s); {
c := s[i]
if c < utf8.RuneSelf {
i++
invalid = false
if c != 0 {
b = append(b, c)
}
continue
}
_, wid := utf8.DecodeRune(s[i:])
if wid == 1 {
i++
if !invalid {
invalid = true
b = append(b, '?')
}
continue
}
invalid = false
b = append(b, s[i:i+wid]...)
i += wid
}
return b
}

func errIncorrectTemporal(date string, in []byte) error {
return vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValue, "Incorrect %s value: '%s'", date, sanitizeErrorValue(in))
}
Expand Down
Loading

0 comments on commit 5989efb

Please sign in to comment.