storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/s3select/sql/stringfuncs.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2019 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package sql
    18  
    19  import (
    20  	"errors"
    21  	"strings"
    22  )
    23  
    24  var (
    25  	errMalformedEscapeSequence  = errors.New("Malformed escape sequence in LIKE clause")
    26  	errInvalidTrimArg           = errors.New("Trim argument is invalid - this should not happen")
    27  	errInvalidSubstringIndexLen = errors.New("Substring start index or length falls outside the string")
    28  )
    29  
    30  const (
    31  	percent    rune = '%'
    32  	underscore rune = '_'
    33  	runeZero   rune = 0
    34  )
    35  
    36  func evalSQLLike(text, pattern string, escape rune) (match bool, err error) {
    37  	s := []rune{}
    38  	prev := runeZero
    39  	hasLeadingPercent := false
    40  	patLen := len([]rune(pattern))
    41  	for i, r := range pattern {
    42  		if i > 0 && prev == escape {
    43  			switch r {
    44  			case percent, escape, underscore:
    45  				s = append(s, r)
    46  				prev = r
    47  				if r == escape {
    48  					prev = runeZero
    49  				}
    50  			default:
    51  				return false, errMalformedEscapeSequence
    52  			}
    53  			continue
    54  		}
    55  
    56  		prev = r
    57  
    58  		var ok bool
    59  		switch r {
    60  		case percent:
    61  			if len(s) == 0 {
    62  				hasLeadingPercent = true
    63  				continue
    64  			}
    65  
    66  			text, ok = matcher(text, string(s), hasLeadingPercent)
    67  			if !ok {
    68  				return false, nil
    69  			}
    70  			hasLeadingPercent = true
    71  			s = []rune{}
    72  
    73  			if i == patLen-1 {
    74  				// Last pattern character is a %, so
    75  				// we are done.
    76  				return true, nil
    77  			}
    78  
    79  		case underscore:
    80  			if len(s) == 0 {
    81  				text, ok = dropRune(text)
    82  				if !ok {
    83  					return false, nil
    84  				}
    85  				continue
    86  			}
    87  
    88  			text, ok = matcher(text, string(s), hasLeadingPercent)
    89  			if !ok {
    90  				return false, nil
    91  			}
    92  			hasLeadingPercent = false
    93  
    94  			text, ok = dropRune(text)
    95  			if !ok {
    96  				return false, nil
    97  			}
    98  			s = []rune{}
    99  
   100  		case escape:
   101  			if i == patLen-1 {
   102  				return false, errMalformedEscapeSequence
   103  			}
   104  			// Otherwise do nothing.
   105  
   106  		default:
   107  			s = append(s, r)
   108  		}
   109  
   110  	}
   111  	if hasLeadingPercent {
   112  		return strings.HasSuffix(text, string(s)), nil
   113  	}
   114  	return string(s) == text, nil
   115  }
   116  
   117  // matcher - Finds `pat` in `text`, and returns the part remainder of
   118  // `text`, after the match. If leadingPercent is false, `pat` must be
   119  // the prefix of `text`, otherwise it must be a substring.
   120  func matcher(text, pat string, leadingPercent bool) (res string, match bool) {
   121  	if !leadingPercent {
   122  		res = strings.TrimPrefix(text, pat)
   123  		if len(text) == len(res) {
   124  			return "", false
   125  		}
   126  	} else {
   127  		parts := strings.SplitN(text, pat, 2)
   128  		if len(parts) == 1 {
   129  			return "", false
   130  		}
   131  		res = parts[1]
   132  	}
   133  	return res, true
   134  }
   135  
   136  func dropRune(text string) (res string, ok bool) {
   137  	r := []rune(text)
   138  	if len(r) == 0 {
   139  		return "", false
   140  	}
   141  	return string(r[1:]), true
   142  }
   143  
   144  func evalSQLSubstring(s string, startIdx, length int) (res string, err error) {
   145  	rs := []rune(s)
   146  
   147  	// According to s3 document, if startIdx < 1, it is set to 1.
   148  	if startIdx < 1 {
   149  		startIdx = 1
   150  	}
   151  
   152  	if startIdx > len(rs) {
   153  		startIdx = len(rs) + 1
   154  	}
   155  
   156  	// StartIdx is 1-based in the input
   157  	startIdx--
   158  	endIdx := len(rs)
   159  	if length != -1 {
   160  		if length < 0 {
   161  			return "", errInvalidSubstringIndexLen
   162  		}
   163  
   164  		if length > (endIdx - startIdx) {
   165  			length = endIdx - startIdx
   166  		}
   167  
   168  		endIdx = startIdx + length
   169  	}
   170  
   171  	return string(rs[startIdx:endIdx]), nil
   172  }
   173  
   174  const (
   175  	trimLeading  = "LEADING"
   176  	trimTrailing = "TRAILING"
   177  	trimBoth     = "BOTH"
   178  )
   179  
   180  func evalSQLTrim(where *string, trimChars, text string) (result string, err error) {
   181  	cutSet := " "
   182  	if trimChars != "" {
   183  		cutSet = trimChars
   184  	}
   185  
   186  	trimFunc := strings.Trim
   187  	switch {
   188  	case where == nil:
   189  	case *where == trimBoth:
   190  	case *where == trimLeading:
   191  		trimFunc = strings.TrimLeft
   192  	case *where == trimTrailing:
   193  		trimFunc = strings.TrimRight
   194  	default:
   195  		return "", errInvalidTrimArg
   196  	}
   197  
   198  	return trimFunc(text, cutSet), nil
   199  }