Skip to content

Commit 8586b6d

Browse files
authored
evalEngine: Implement string INSERT (#15201)
Signed-off-by: Noble Mittal <noblemittal@outlook.com>
1 parent fcd5ad4 commit 8586b6d

File tree

8 files changed

+273
-9
lines changed

8 files changed

+273
-9
lines changed

go/vt/vtgate/evalengine/cached_size.go

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/vt/vtgate/evalengine/compiler.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,15 @@ func (c *compiler) compileNullCheck3(arg1, arg2, arg3 ctype) *jump {
327327
return nil
328328
}
329329

330+
func (c *compiler) compileNullCheck4(arg1, arg2, arg3, arg4 ctype) *jump {
331+
if arg1.nullable() || arg2.nullable() || arg3.nullable() || arg4.nullable() {
332+
j := c.asm.jumpFrom()
333+
c.asm.NullCheck4(j)
334+
return j
335+
}
336+
return nil
337+
}
338+
330339
func (c *compiler) compileNullCheckArg(ct ctype, offset int) *jump {
331340
if ct.nullable() {
332341
j := c.asm.jumpFrom()

go/vt/vtgate/evalengine/compiler_asm.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2345,6 +2345,28 @@ func (asm *assembler) Fn_BIT_LENGTH() {
23452345
}, "FN BIT_LENGTH VARCHAR(SP-1)")
23462346
}
23472347

2348+
func (asm *assembler) Fn_INSERT(col collations.TypedCollation) {
2349+
asm.adjustStack(-3)
2350+
2351+
asm.emit(func(env *ExpressionEnv) int {
2352+
str := env.vm.stack[env.vm.sp-4].(*evalBytes)
2353+
pos := env.vm.stack[env.vm.sp-3].(*evalInt64).i
2354+
l := env.vm.stack[env.vm.sp-2].(*evalInt64).i
2355+
newstr := env.vm.stack[env.vm.sp-1].(*evalBytes)
2356+
2357+
res := insert(str, newstr, int(pos), int(l))
2358+
if !validMaxLength(int64(len(res)), 1) {
2359+
env.vm.stack[env.vm.sp-4] = nil
2360+
env.vm.sp -= 3
2361+
return 1
2362+
}
2363+
2364+
env.vm.stack[env.vm.sp-4] = env.vm.arena.newEvalText(res, col)
2365+
env.vm.sp -= 3
2366+
return 1
2367+
}, "FN INSERT VARCHAR(SP-4) INT64(SP-3) INT64(SP-2) VARCHAR(SP-1)")
2368+
}
2369+
23482370
func (asm *assembler) Fn_LUCASE(upcase bool) {
23492371
if upcase {
23502372
asm.emit(func(env *ExpressionEnv) int {
@@ -3147,6 +3169,17 @@ func (asm *assembler) NullCheck3(j *jump) {
31473169
}, "NULLCHECK SP-1, SP-2, SP-3")
31483170
}
31493171

3172+
func (asm *assembler) NullCheck4(j *jump) {
3173+
asm.emit(func(env *ExpressionEnv) int {
3174+
if env.vm.stack[env.vm.sp-4] == nil || env.vm.stack[env.vm.sp-3] == nil || env.vm.stack[env.vm.sp-2] == nil || env.vm.stack[env.vm.sp-1] == nil {
3175+
env.vm.stack[env.vm.sp-4] = nil
3176+
env.vm.sp -= 3
3177+
return j.offset()
3178+
}
3179+
return 1
3180+
}, "NULLCHECK SP-1, SP-2, SP-3, SP-4")
3181+
}
3182+
31503183
func (asm *assembler) NullCheckArg(j *jump, offset int) {
31513184
asm.emit(func(env *ExpressionEnv) int {
31523185
if env.vm.stack[env.vm.sp-1] == nil {

go/vt/vtgate/evalengine/fn_string.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package evalengine
1818

1919
import (
2020
"bytes"
21+
"math"
2122

2223
"vitess.io/vitess/go/mysql/collations"
2324
"vitess.io/vitess/go/mysql/collations/charset"
@@ -29,6 +30,11 @@ import (
2930
)
3031

3132
type (
33+
builtinInsert struct {
34+
CallExpr
35+
collate collations.ID
36+
}
37+
3238
builtinChangeCase struct {
3339
CallExpr
3440
upcase bool
@@ -106,6 +112,7 @@ type (
106112
}
107113
)
108114

115+
var _ IR = (*builtinInsert)(nil)
109116
var _ IR = (*builtinChangeCase)(nil)
110117
var _ IR = (*builtinCharLength)(nil)
111118
var _ IR = (*builtinLength)(nil)
@@ -120,6 +127,122 @@ var _ IR = (*builtinLeftRight)(nil)
120127
var _ IR = (*builtinPad)(nil)
121128
var _ IR = (*builtinTrim)(nil)
122129

130+
func insert(str, newstr *evalBytes, pos, l int) []byte {
131+
pos--
132+
133+
cs := colldata.Lookup(str.col.Collation).Charset()
134+
strLen := charset.Length(cs, str.bytes)
135+
136+
if pos < 0 || strLen <= pos {
137+
return str.bytes
138+
}
139+
if l < 0 {
140+
l = strLen
141+
}
142+
143+
front := charset.Slice(cs, str.bytes, 0, pos)
144+
var back []byte
145+
if pos <= math.MaxInt-l && pos+l < strLen {
146+
back = charset.Slice(cs, str.bytes, pos+l, strLen)
147+
}
148+
149+
res := make([]byte, len(front)+len(newstr.bytes)+len(back))
150+
151+
copy(res[:len(front)], front)
152+
copy(res[len(front):], newstr.bytes)
153+
copy(res[len(front)+len(newstr.bytes):], back)
154+
155+
return res
156+
}
157+
158+
func (call *builtinInsert) eval(env *ExpressionEnv) (eval, error) {
159+
args, err := call.args(env)
160+
if err != nil {
161+
return nil, err
162+
}
163+
if args[0] == nil || args[1] == nil || args[2] == nil || args[3] == nil {
164+
return nil, nil
165+
}
166+
167+
str, ok := args[0].(*evalBytes)
168+
if !ok {
169+
str, err = evalToVarchar(args[0], call.collate, true)
170+
if err != nil {
171+
return nil, err
172+
}
173+
}
174+
175+
pos := evalToInt64(args[1]).i
176+
l := evalToInt64(args[2]).i
177+
178+
newstr, err := evalToVarchar(args[3], str.col.Collation, true)
179+
if err != nil {
180+
return nil, err
181+
}
182+
183+
res := insert(str, newstr, int(pos), int(l))
184+
if !validMaxLength(int64(len(res)), 1) {
185+
return nil, nil
186+
}
187+
return newEvalText(res, str.col), nil
188+
}
189+
190+
func (call *builtinInsert) compile(c *compiler) (ctype, error) {
191+
str, err := call.Arguments[0].compile(c)
192+
if err != nil {
193+
return ctype{}, err
194+
}
195+
196+
pos, err := call.Arguments[1].compile(c)
197+
if err != nil {
198+
return ctype{}, err
199+
}
200+
201+
l, err := call.Arguments[2].compile(c)
202+
if err != nil {
203+
return ctype{}, err
204+
}
205+
206+
newstr, err := call.Arguments[3].compile(c)
207+
if err != nil {
208+
return ctype{}, err
209+
}
210+
211+
skip := c.compileNullCheck4(str, pos, l, newstr)
212+
213+
_ = c.compileToInt64(pos, 3)
214+
_ = c.compileToInt64(l, 2)
215+
216+
if err != nil {
217+
return ctype{}, nil
218+
}
219+
220+
col := str.Col
221+
222+
switch {
223+
case str.isTextual():
224+
default:
225+
c.asm.Convert_xce(4, sqltypes.VarChar, c.collation)
226+
col = typedCoercionCollation(sqltypes.VarChar, c.collation)
227+
}
228+
229+
switch {
230+
case newstr.isTextual():
231+
fromCharset := colldata.Lookup(newstr.Col.Collation).Charset()
232+
toCharset := colldata.Lookup(col.Collation).Charset()
233+
if fromCharset != toCharset && !toCharset.IsSuperset(fromCharset) {
234+
c.asm.Convert_xce(1, sqltypes.VarChar, col.Collation)
235+
}
236+
default:
237+
c.asm.Convert_xce(1, sqltypes.VarChar, col.Collation)
238+
}
239+
240+
c.asm.Fn_INSERT(col)
241+
c.asm.jumpDestination(skip)
242+
243+
return ctype{Type: sqltypes.VarChar, Col: col, Flag: flagNullable}, nil
244+
}
245+
123246
func (call *builtinChangeCase) eval(env *ExpressionEnv) (eval, error) {
124247
arg, err := call.arg1(env)
125248
if err != nil {

go/vt/vtgate/evalengine/testcases/cases.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ var Cases = []TestCase{
6363
{Run: TupleComparisons},
6464
{Run: Comparisons},
6565
{Run: InStatement},
66+
{Run: FnInsert},
6667
{Run: FnLower},
6768
{Run: FnUpper},
6869
{Run: FnCharLength},
@@ -1314,6 +1315,28 @@ var JSONExtract_Schema = []*querypb.Field{
13141315
},
13151316
}
13161317

1318+
func FnInsert(yield Query) {
1319+
for _, s := range insertStrings {
1320+
for _, ns := range insertStrings {
1321+
for _, l := range inputBitwise {
1322+
for _, p := range inputBitwise {
1323+
yield(fmt.Sprintf("INSERT(%s, %s, %s, %s)", s, p, l, ns), nil)
1324+
}
1325+
}
1326+
}
1327+
}
1328+
1329+
mysqlDocSamples := []string{
1330+
"INSERT('Quadratic', 3, 4, 'What')",
1331+
"INSERT('Quadratic', -1, 4, 'What')",
1332+
"INSERT('Quadratic', 3, 100, 'What')",
1333+
}
1334+
1335+
for _, q := range mysqlDocSamples {
1336+
yield(q, nil)
1337+
}
1338+
}
1339+
13171340
func FnLower(yield Query) {
13181341
for _, str := range inputStrings {
13191342
yield(fmt.Sprintf("LOWER(%s)", str), nil)

go/vt/vtgate/evalengine/testcases/inputs.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,41 @@ var inputStrings = []string{
199199
// "_ucs2 'AabcÅå'",
200200
}
201201

202+
var insertStrings = []string{
203+
"NULL",
204+
"\"\"",
205+
"\"a\"",
206+
"\"abc\"",
207+
"1",
208+
"-1",
209+
"0123",
210+
"0xAACC",
211+
"3.1415926",
212+
// MySQL has broken behavior for these inputs,
213+
// see https://github.com/mysql/mysql-server/pull/517
214+
// "\"Å å\"",
215+
// "\"中文测试\"",
216+
// "\"日本語テスト\"",
217+
// "\"한국어 시험\"",
218+
// "\"😊😂🤢\"",
219+
// "_utf8mb4 'abcABCÅå'",
220+
"DATE '2022-10-11'",
221+
"TIME '11:02:23'",
222+
"'123'",
223+
"9223372036854775807",
224+
"-9223372036854775808",
225+
"999999999999999999999999",
226+
"-999999999999999999999999",
227+
"_binary 'Müller' ",
228+
"_latin1 0xFF",
229+
// TODO: support other multibyte encodings
230+
// "_dec8 'ÒòÅå'",
231+
// "_utf8mb3 'abcABCÅå'",
232+
// "_utf16 'AabcÅå'",
233+
// "_utf32 'AabcÅå'",
234+
// "_ucs2 'AabcÅå'",
235+
}
236+
202237
var inputConversionTypes = []string{
203238
"BINARY", "BINARY(1)", "BINARY(0)", "BINARY(16)", "BINARY(-1)",
204239
"CHAR", "CHAR(1)", "CHAR(0)", "CHAR(16)", "CHAR(-1)",

go/vt/vtgate/evalengine/translate_builtin.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,35 @@ func (ast *astCompiler) translateCallable(call sqlparser.Callable) (IR, error) {
984984
return &builtinRegexpReplace{
985985
CallExpr: CallExpr{Arguments: args, Method: "REGEXP_REPLACE"},
986986
}, nil
987+
988+
case *sqlparser.InsertExpr:
989+
str, err := ast.translateExpr(call.Str)
990+
if err != nil {
991+
return nil, err
992+
}
993+
994+
pos, err := ast.translateExpr(call.Pos)
995+
if err != nil {
996+
return nil, err
997+
}
998+
999+
len, err := ast.translateExpr(call.Len)
1000+
if err != nil {
1001+
return nil, err
1002+
}
1003+
1004+
newstr, err := ast.translateExpr(call.NewStr)
1005+
if err != nil {
1006+
return nil, err
1007+
}
1008+
1009+
args := []IR{str, pos, len, newstr}
1010+
1011+
var cexpr = CallExpr{Arguments: args, Method: "INSERT"}
1012+
return &builtinInsert{
1013+
CallExpr: cexpr,
1014+
collate: ast.cfg.Collation,
1015+
}, nil
9871016
default:
9881017
return nil, translateExprNotSupported(call)
9891018
}

go/vt/vtgate/planbuilder/testdata/select_cases.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3048,15 +3048,15 @@
30483048
"QueryType": "SELECT",
30493049
"Original": "select insert('Quadratic', 3, 4, 'What')",
30503050
"Instructions": {
3051-
"OperatorType": "Route",
3052-
"Variant": "Reference",
3053-
"Keyspace": {
3054-
"Name": "main",
3055-
"Sharded": false
3056-
},
3057-
"FieldQuery": "select insert('Quadratic', 3, 4, 'What') from dual where 1 != 1",
3058-
"Query": "select insert('Quadratic', 3, 4, 'What') from dual",
3059-
"Table": "dual"
3051+
"OperatorType": "Projection",
3052+
"Expressions": [
3053+
"'QuWhattic' as insert('Quadratic', 3, 4, 'What')"
3054+
],
3055+
"Inputs": [
3056+
{
3057+
"OperatorType": "SingleRow"
3058+
}
3059+
]
30603060
},
30613061
"TablesUsed": [
30623062
"main.dual"

0 commit comments

Comments
 (0)