github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/evaluator/evaluator_like.go (about)

     1  // Copyright 2015 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package evaluator
    15  
    16  import (
    17  	"regexp"
    18  
    19  	"github.com/insionng/yougam/libraries/juju/errors"
    20  	"github.com/insionng/yougam/libraries/pingcap/tidb/ast"
    21  	"github.com/insionng/yougam/libraries/pingcap/tidb/util/types"
    22  )
    23  
    24  const (
    25  	patMatch = iota + 1
    26  	patOne
    27  	patAny
    28  )
    29  
    30  // handle escapes and wild cards convert pattern characters and pattern types,
    31  func compilePattern(pattern string, escape byte) (patChars, patTypes []byte) {
    32  	var lastAny bool
    33  	patChars = make([]byte, len(pattern))
    34  	patTypes = make([]byte, len(pattern))
    35  	patLen := 0
    36  	for i := 0; i < len(pattern); i++ {
    37  		var tp byte
    38  		var c = pattern[i]
    39  		switch c {
    40  		case escape:
    41  			lastAny = false
    42  			tp = patMatch
    43  			if i < len(pattern)-1 {
    44  				i++
    45  				c = pattern[i]
    46  				if c == escape || c == '_' || c == '%' {
    47  					// valid escape.
    48  				} else {
    49  					// invalid escape, fall back to escape byte
    50  					// mysql will treat escape character as the origin value even
    51  					// the escape sequence is invalid in Go or C.
    52  					// e.g, \m is invalid in Go, but in MySQL we will get "m" for select '\m'.
    53  					// Following case is correct just for escape \, not for others like +.
    54  					// TODO: add more checks for other escapes.
    55  					i--
    56  					c = escape
    57  				}
    58  			}
    59  		case '_':
    60  			lastAny = false
    61  			tp = patOne
    62  		case '%':
    63  			if lastAny {
    64  				continue
    65  			}
    66  			lastAny = true
    67  			tp = patAny
    68  		default:
    69  			lastAny = false
    70  			tp = patMatch
    71  		}
    72  		patChars[patLen] = c
    73  		patTypes[patLen] = tp
    74  		patLen++
    75  	}
    76  	for i := 0; i < patLen-1; i++ {
    77  		if (patTypes[i] == patAny) && (patTypes[i+1] == patOne) {
    78  			patTypes[i] = patOne
    79  			patTypes[i+1] = patAny
    80  		}
    81  	}
    82  	patChars = patChars[:patLen]
    83  	patTypes = patTypes[:patLen]
    84  	return
    85  }
    86  
    87  const caseDiff = 'a' - 'A'
    88  
    89  func matchByteCI(a, b byte) bool {
    90  	if a == b {
    91  		return true
    92  	}
    93  	if a >= 'a' && a <= 'z' && a-caseDiff == b {
    94  		return true
    95  	}
    96  	return a >= 'A' && a <= 'Z' && a+caseDiff == b
    97  }
    98  
    99  func doMatch(str string, patChars, patTypes []byte) bool {
   100  	var sIdx int
   101  	for i := 0; i < len(patChars); i++ {
   102  		switch patTypes[i] {
   103  		case patMatch:
   104  			if sIdx >= len(str) || !matchByteCI(str[sIdx], patChars[i]) {
   105  				return false
   106  			}
   107  			sIdx++
   108  		case patOne:
   109  			sIdx++
   110  			if sIdx > len(str) {
   111  				return false
   112  			}
   113  		case patAny:
   114  			i++
   115  			if i == len(patChars) {
   116  				return true
   117  			}
   118  			for sIdx < len(str) {
   119  				if matchByteCI(patChars[i], str[sIdx]) && doMatch(str[sIdx:], patChars[i:], patTypes[i:]) {
   120  					return true
   121  				}
   122  				sIdx++
   123  			}
   124  			return false
   125  		}
   126  	}
   127  	return sIdx == len(str)
   128  }
   129  
   130  func (e *Evaluator) patternLike(p *ast.PatternLikeExpr) bool {
   131  	expr := p.Expr.GetDatum()
   132  	if expr.Kind() == types.KindNull {
   133  		p.SetNull()
   134  		return true
   135  	}
   136  
   137  	sexpr, err := expr.ToString()
   138  	if err != nil {
   139  		e.err = errors.Trace(err)
   140  		return false
   141  	}
   142  
   143  	// We need to compile pattern if it has not been compiled or it is not static.
   144  	var needCompile = len(p.PatChars) == 0 || !ast.IsConstant(p.Pattern)
   145  	if needCompile {
   146  		pattern := p.Pattern.GetDatum()
   147  		if pattern.Kind() == types.KindNull {
   148  			p.SetNull()
   149  			return true
   150  		}
   151  		spattern, err := pattern.ToString()
   152  		if err != nil {
   153  			e.err = errors.Trace(err)
   154  			return false
   155  		}
   156  		p.PatChars, p.PatTypes = compilePattern(spattern, p.Escape)
   157  	}
   158  	match := doMatch(sexpr, p.PatChars, p.PatTypes)
   159  	if p.Not {
   160  		match = !match
   161  	}
   162  	p.SetInt64(boolToInt64(match))
   163  	return true
   164  }
   165  
   166  func (e *Evaluator) patternRegexp(p *ast.PatternRegexpExpr) bool {
   167  	var sexpr string
   168  	if p.Sexpr != nil {
   169  		sexpr = *p.Sexpr
   170  	} else {
   171  		expr := p.Expr.GetDatum()
   172  		if expr.Kind() == types.KindNull {
   173  			p.SetNull()
   174  			return true
   175  		}
   176  		var err error
   177  		sexpr, err = expr.ToString()
   178  		if err != nil {
   179  			e.err = errors.Errorf("non-string Expression in LIKE: %v (Value of type %T)", expr, expr)
   180  			return false
   181  		}
   182  
   183  		if ast.IsConstant(p.Expr) {
   184  			p.Sexpr = new(string)
   185  			*p.Sexpr = sexpr
   186  		}
   187  	}
   188  
   189  	re := p.Re
   190  	if re == nil {
   191  		pattern := p.Pattern.GetDatum()
   192  		if pattern.Kind() == types.KindNull {
   193  			p.SetNull()
   194  			return true
   195  		}
   196  		spattern, err := pattern.ToString()
   197  		if err != nil {
   198  			e.err = errors.Errorf("non-string pattern in LIKE: %v (Value of type %T)", pattern, pattern)
   199  			return false
   200  		}
   201  
   202  		if re, err = regexp.Compile(spattern); err != nil {
   203  			e.err = errors.Trace(err)
   204  			return false
   205  		}
   206  
   207  		if ast.IsConstant(p.Pattern) {
   208  			p.Re = re
   209  		}
   210  	}
   211  	match := re.MatchString(sexpr)
   212  	if p.Not {
   213  		match = !match
   214  	}
   215  	p.SetInt64(boolToInt64(match))
   216  	return true
   217  }