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  }