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 }