github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/evaluator/builtin_string.go (about) 1 // Copyright 2013 The ql Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSES/QL-LICENSE file. 4 5 // Copyright 2015 PingCAP, Inc. 6 // 7 // Licensed under the Apache License, Version 2.0 (the "License"); 8 // you may not use this file except in compliance with the License. 9 // You may obtain a copy of the License at 10 // 11 // http://www.apache.org/licenses/LICENSE-2.0 12 // 13 // Unless required by applicable law or agreed to in writing, software 14 // distributed under the License is distributed on an "AS IS" BASIS, 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package evaluator 19 20 import ( 21 "fmt" 22 "strings" 23 24 "github.com/insionng/yougam/libraries/juju/errors" 25 "github.com/insionng/yougam/libraries/ngaut/log" 26 "github.com/insionng/yougam/libraries/pingcap/tidb/ast" 27 "github.com/insionng/yougam/libraries/pingcap/tidb/context" 28 "github.com/insionng/yougam/libraries/pingcap/tidb/util/charset" 29 "github.com/insionng/yougam/libraries/pingcap/tidb/util/stringutil" 30 "github.com/insionng/yougam/libraries/pingcap/tidb/util/types" 31 "github.com/insionng/yougam/libraries/x/text/transform" 32 ) 33 34 // https://dev.mysql.com/doc/refman/5.7/en/string-functions.html 35 36 func builtinLength(args []types.Datum, _ context.Context) (d types.Datum, err error) { 37 switch args[0].Kind() { 38 case types.KindNull: 39 return d, nil 40 default: 41 s, err := args[0].ToString() 42 if err != nil { 43 return d, errors.Trace(err) 44 } 45 d.SetInt64(int64(len(s))) 46 return d, nil 47 } 48 } 49 50 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_ascii 51 func builtinASCII(args []types.Datum, _ context.Context) (d types.Datum, err error) { 52 switch args[0].Kind() { 53 case types.KindNull: 54 return d, nil 55 default: 56 s, err := args[0].ToString() 57 if err != nil { 58 return d, errors.Trace(err) 59 } 60 if len(s) == 0 { 61 d.SetInt64(0) 62 return d, nil 63 } 64 d.SetInt64(int64(s[0])) 65 return d, nil 66 } 67 } 68 69 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat 70 func builtinConcat(args []types.Datum, _ context.Context) (d types.Datum, err error) { 71 var s []byte 72 for _, a := range args { 73 if a.Kind() == types.KindNull { 74 return d, nil 75 } 76 var ss string 77 ss, err = a.ToString() 78 if err != nil { 79 return d, errors.Trace(err) 80 } 81 s = append(s, []byte(ss)...) 82 } 83 d.SetBytesAsString(s) 84 return d, nil 85 } 86 87 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat-ws 88 func builtinConcatWS(args []types.Datum, _ context.Context) (d types.Datum, err error) { 89 var sep string 90 s := make([]string, 0, len(args)) 91 for i, a := range args { 92 if a.Kind() == types.KindNull { 93 if i == 0 { 94 return d, nil 95 } 96 continue 97 } 98 ss, err := a.ToString() 99 if err != nil { 100 return d, errors.Trace(err) 101 } 102 103 if i == 0 { 104 sep = ss 105 continue 106 } 107 s = append(s, ss) 108 } 109 110 d.SetString(strings.Join(s, sep)) 111 return d, nil 112 } 113 114 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_left 115 func builtinLeft(args []types.Datum, _ context.Context) (d types.Datum, err error) { 116 str, err := args[0].ToString() 117 if err != nil { 118 return d, errors.Trace(err) 119 } 120 length, err := args[1].ToInt64() 121 if err != nil { 122 return d, errors.Trace(err) 123 } 124 l := int(length) 125 if l < 0 { 126 l = 0 127 } else if l > len(str) { 128 l = len(str) 129 } 130 d.SetString(str[:l]) 131 return d, nil 132 } 133 134 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_repeat 135 func builtinRepeat(args []types.Datum, _ context.Context) (d types.Datum, err error) { 136 str, err := args[0].ToString() 137 if err != nil { 138 return d, err 139 } 140 ch := fmt.Sprintf("%v", str) 141 num := 0 142 x := args[1] 143 switch x.Kind() { 144 case types.KindInt64: 145 num = int(x.GetInt64()) 146 case types.KindUint64: 147 num = int(x.GetUint64()) 148 } 149 if num < 1 { 150 d.SetString("") 151 return d, nil 152 } 153 d.SetString(strings.Repeat(ch, num)) 154 return d, nil 155 } 156 157 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_lower 158 func builtinLower(args []types.Datum, _ context.Context) (d types.Datum, err error) { 159 x := args[0] 160 switch x.Kind() { 161 case types.KindNull: 162 return d, nil 163 default: 164 s, err := x.ToString() 165 if err != nil { 166 return d, errors.Trace(err) 167 } 168 d.SetString(strings.ToLower(s)) 169 return d, nil 170 } 171 } 172 173 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_reverse 174 func builtinReverse(args []types.Datum, _ context.Context) (d types.Datum, err error) { 175 x := args[0] 176 switch x.Kind() { 177 case types.KindNull: 178 return d, nil 179 default: 180 s, err := x.ToString() 181 if err != nil { 182 return d, errors.Trace(err) 183 } 184 d.SetString(stringutil.Reverse(s)) 185 return d, nil 186 } 187 } 188 189 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_upper 190 func builtinUpper(args []types.Datum, _ context.Context) (d types.Datum, err error) { 191 x := args[0] 192 switch x.Kind() { 193 case types.KindNull: 194 return d, nil 195 default: 196 s, err := x.ToString() 197 if err != nil { 198 return d, errors.Trace(err) 199 } 200 d.SetString(strings.ToUpper(s)) 201 return d, nil 202 } 203 } 204 205 // See: https://dev.mysql.com/doc/refman/5.7/en/string-comparison-functions.html 206 func builtinStrcmp(args []types.Datum, _ context.Context) (d types.Datum, err error) { 207 if args[0].Kind() == types.KindNull || args[1].Kind() == types.KindNull { 208 return d, nil 209 } 210 left, err := args[0].ToString() 211 if err != nil { 212 return d, errors.Trace(err) 213 } 214 right, err := args[1].ToString() 215 if err != nil { 216 return d, errors.Trace(err) 217 } 218 res := types.CompareString(left, right) 219 d.SetInt64(int64(res)) 220 return d, nil 221 } 222 223 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_replace 224 func builtinReplace(args []types.Datum, _ context.Context) (d types.Datum, err error) { 225 for _, arg := range args { 226 if arg.Kind() == types.KindNull { 227 return d, nil 228 } 229 } 230 231 str, err := args[0].ToString() 232 if err != nil { 233 return d, errors.Trace(err) 234 } 235 oldStr, err := args[1].ToString() 236 if err != nil { 237 return d, errors.Trace(err) 238 } 239 newStr, err := args[2].ToString() 240 if err != nil { 241 return d, errors.Trace(err) 242 } 243 d.SetString(strings.Replace(str, oldStr, newStr, -1)) 244 245 return d, nil 246 } 247 248 // See: https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert 249 func builtinConvert(args []types.Datum, _ context.Context) (d types.Datum, err error) { 250 // Casting nil to any type returns nil 251 if args[0].Kind() != types.KindString { 252 return d, nil 253 } 254 255 str := args[0].GetString() 256 Charset := args[1].GetString() 257 258 if strings.ToLower(Charset) == "ascii" { 259 d.SetString(str) 260 return d, nil 261 } else if strings.ToLower(Charset) == "utf8mb4" { 262 d.SetString(str) 263 return d, nil 264 } 265 266 encoding, _ := charset.Lookup(Charset) 267 if encoding == nil { 268 return d, errors.Errorf("unknown encoding: %s", Charset) 269 } 270 271 target, _, err := transform.String(encoding.NewDecoder(), str) 272 if err != nil { 273 log.Errorf("Convert %s to %s with error: %v", str, Charset, err) 274 return d, errors.Trace(err) 275 } 276 d.SetString(target) 277 return d, nil 278 } 279 280 func builtinSubstring(args []types.Datum, _ context.Context) (d types.Datum, err error) { 281 // The meaning of the elements of args. 282 // arg[0] -> StrExpr 283 // arg[1] -> Pos 284 // arg[2] -> Len (Optional) 285 str, err := args[0].ToString() 286 if err != nil { 287 return d, errors.Errorf("Substring invalid args, need string but get %T", args[0].GetValue()) 288 } 289 290 if args[1].Kind() != types.KindInt64 { 291 return d, errors.Errorf("Substring invalid pos args, need int but get %T", args[1].GetValue()) 292 } 293 pos := args[1].GetInt64() 294 295 length, hasLen := int64(-1), false 296 if len(args) == 3 { 297 if args[2].Kind() != types.KindInt64 { 298 return d, errors.Errorf("Substring invalid pos args, need int but get %T", args[2].GetValue()) 299 } 300 length, hasLen = args[2].GetInt64(), true 301 } 302 // The forms without a len argument return a substring from string str starting at position pos. 303 // The forms with a len argument return a substring len characters long from string str, starting at position pos. 304 // The forms that use FROM are standard SQL syntax. It is also possible to use a negative value for pos. 305 // In this case, the beginning of the substring is pos characters from the end of the string, rather than the beginning. 306 // A negative value may be used for pos in any of the forms of this function. 307 if pos < 0 { 308 pos = int64(len(str)) + pos 309 } else { 310 pos-- 311 } 312 if pos > int64(len(str)) || pos < int64(0) { 313 pos = int64(len(str)) 314 } 315 if hasLen { 316 if end := pos + length; end < pos { 317 d.SetString("") 318 } else if end > int64(len(str)) { 319 d.SetString(str[pos:]) 320 } else { 321 d.SetString(str[pos:end]) 322 } 323 } else { 324 d.SetString(str[pos:]) 325 } 326 return d, nil 327 } 328 329 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_substring-index 330 func builtinSubstringIndex(args []types.Datum, _ context.Context) (d types.Datum, err error) { 331 // The meaning of the elements of args. 332 // args[0] -> StrExpr 333 // args[1] -> Delim 334 // args[2] -> Count 335 str, err := args[0].ToString() 336 if err != nil { 337 return d, errors.Errorf("Substring_Index invalid args, need string but get %T", args[0].GetValue()) 338 } 339 340 delim, err := args[1].ToString() 341 if err != nil { 342 return d, errors.Errorf("Substring_Index invalid delim, need string but get %T", args[1].GetValue()) 343 } 344 if len(delim) == 0 { 345 d.SetString("") 346 return d, nil 347 } 348 349 c, err := args[2].ToInt64() 350 if err != nil { 351 return d, errors.Trace(err) 352 } 353 count := int(c) 354 strs := strings.Split(str, delim) 355 var ( 356 start = 0 357 end = len(strs) 358 ) 359 if count > 0 { 360 // If count is positive, everything to the left of the final delimiter (counting from the left) is returned. 361 if count < end { 362 end = count 363 } 364 } else { 365 // If count is negative, everything to the right of the final delimiter (counting from the right) is returned. 366 count = -count 367 if count < end { 368 start = end - count 369 } 370 } 371 substrs := strs[start:end] 372 d.SetString(strings.Join(substrs, delim)) 373 return d, nil 374 } 375 376 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_locate 377 func builtinLocate(args []types.Datum, _ context.Context) (d types.Datum, err error) { 378 // The meaning of the elements of args. 379 // args[0] -> SubStr 380 // args[1] -> Str 381 // args[2] -> Pos 382 // eval str 383 if args[1].Kind() == types.KindNull { 384 return d, nil 385 } 386 str, err := args[1].ToString() 387 if err != nil { 388 return d, errors.Trace(err) 389 } 390 // eval substr 391 if args[0].Kind() == types.KindNull { 392 return d, nil 393 } 394 subStr, err := args[0].ToString() 395 if err != nil { 396 return d, errors.Trace(err) 397 } 398 // eval pos 399 pos := int64(0) 400 if len(args) == 3 { 401 p, err := args[2].ToInt64() 402 if err != nil { 403 return d, errors.Trace(err) 404 } 405 pos = p - 1 406 if pos < 0 || pos > int64(len(str)) { 407 d.SetInt64(0) 408 return d, nil 409 } 410 if pos > int64(len(str)-len(subStr)) { 411 d.SetInt64(0) 412 return d, nil 413 } 414 } 415 if len(subStr) == 0 { 416 d.SetInt64(pos + 1) 417 return d, nil 418 } 419 i := strings.Index(str[pos:], subStr) 420 if i == -1 { 421 d.SetInt64(0) 422 return d, nil 423 } 424 d.SetInt64(int64(i) + pos + 1) 425 return d, nil 426 } 427 428 const spaceChars = "\n\t\r " 429 430 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_trim 431 func builtinTrim(args []types.Datum, _ context.Context) (d types.Datum, err error) { 432 // args[0] -> Str 433 // args[1] -> RemStr 434 // args[2] -> Direction 435 // eval str 436 if args[0].Kind() == types.KindNull { 437 return d, nil 438 } 439 str, err := args[0].ToString() 440 if err != nil { 441 return d, errors.Trace(err) 442 } 443 remstr := "" 444 // eval remstr 445 if len(args) > 1 { 446 if args[1].Kind() != types.KindNull { 447 remstr, err = args[1].ToString() 448 if err != nil { 449 return d, errors.Trace(err) 450 } 451 } 452 } 453 // do trim 454 var result string 455 var direction ast.TrimDirectionType 456 if len(args) > 2 { 457 direction = args[2].GetValue().(ast.TrimDirectionType) 458 } else { 459 direction = ast.TrimBothDefault 460 } 461 if direction == ast.TrimLeading { 462 if len(remstr) > 0 { 463 result = trimLeft(str, remstr) 464 } else { 465 result = strings.TrimLeft(str, spaceChars) 466 } 467 } else if direction == ast.TrimTrailing { 468 if len(remstr) > 0 { 469 result = trimRight(str, remstr) 470 } else { 471 result = strings.TrimRight(str, spaceChars) 472 } 473 } else if len(remstr) > 0 { 474 x := trimLeft(str, remstr) 475 result = trimRight(x, remstr) 476 } else { 477 result = strings.Trim(str, spaceChars) 478 } 479 d.SetString(result) 480 return d, nil 481 } 482 483 // For LTRIM & RTRIM 484 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_ltrim 485 // See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_rtrim 486 func trimFn(fn func(string, string) string, cutset string) BuiltinFunc { 487 return func(args []types.Datum, ctx context.Context) (d types.Datum, err error) { 488 if args[0].Kind() == types.KindNull { 489 return d, nil 490 } 491 str, err := args[0].ToString() 492 if err != nil { 493 return d, errors.Trace(err) 494 } 495 d.SetString(fn(str, cutset)) 496 return d, nil 497 } 498 } 499 500 func trimLeft(str, remstr string) string { 501 for { 502 x := strings.TrimPrefix(str, remstr) 503 if len(x) == len(str) { 504 return x 505 } 506 str = x 507 } 508 } 509 510 func trimRight(str, remstr string) string { 511 for { 512 x := strings.TrimSuffix(str, remstr) 513 if len(x) == len(str) { 514 return x 515 } 516 str = x 517 } 518 }