github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/s3select/sql/stringfuncs.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package sql 19 20 import ( 21 "errors" 22 "strings" 23 ) 24 25 var ( 26 errMalformedEscapeSequence = errors.New("Malformed escape sequence in LIKE clause") 27 errInvalidTrimArg = errors.New("Trim argument is invalid - this should not happen") 28 errInvalidSubstringIndexLen = errors.New("Substring start index or length falls outside the string") 29 ) 30 31 const ( 32 percent rune = '%' 33 underscore rune = '_' 34 runeZero rune = 0 35 ) 36 37 func evalSQLLike(text, pattern string, escape rune) (match bool, err error) { 38 s := []rune{} 39 prev := runeZero 40 hasLeadingPercent := false 41 patLen := len([]rune(pattern)) 42 for i, r := range pattern { 43 if i > 0 && prev == escape { 44 switch r { 45 case percent, escape, underscore: 46 s = append(s, r) 47 prev = r 48 if r == escape { 49 prev = runeZero 50 } 51 default: 52 return false, errMalformedEscapeSequence 53 } 54 continue 55 } 56 57 prev = r 58 59 var ok bool 60 switch r { 61 case percent: 62 if len(s) == 0 { 63 hasLeadingPercent = true 64 continue 65 } 66 67 text, ok = matcher(text, string(s), hasLeadingPercent) 68 if !ok { 69 return false, nil 70 } 71 hasLeadingPercent = true 72 s = []rune{} 73 74 if i == patLen-1 { 75 // Last pattern character is a %, so 76 // we are done. 77 return true, nil 78 } 79 80 case underscore: 81 if len(s) == 0 { 82 text, ok = dropRune(text) 83 if !ok { 84 return false, nil 85 } 86 continue 87 } 88 89 text, ok = matcher(text, string(s), hasLeadingPercent) 90 if !ok { 91 return false, nil 92 } 93 hasLeadingPercent = false 94 95 text, ok = dropRune(text) 96 if !ok { 97 return false, nil 98 } 99 s = []rune{} 100 101 case escape: 102 if i == patLen-1 { 103 return false, errMalformedEscapeSequence 104 } 105 // Otherwise do nothing. 106 107 default: 108 s = append(s, r) 109 } 110 111 } 112 if hasLeadingPercent { 113 return strings.HasSuffix(text, string(s)), nil 114 } 115 return string(s) == text, nil 116 } 117 118 // matcher - Finds `pat` in `text`, and returns the part remainder of 119 // `text`, after the match. If leadingPercent is false, `pat` must be 120 // the prefix of `text`, otherwise it must be a substring. 121 func matcher(text, pat string, leadingPercent bool) (res string, match bool) { 122 if !leadingPercent { 123 res = strings.TrimPrefix(text, pat) 124 if len(text) == len(res) { 125 return "", false 126 } 127 } else { 128 parts := strings.SplitN(text, pat, 2) 129 if len(parts) == 1 { 130 return "", false 131 } 132 res = parts[1] 133 } 134 return res, true 135 } 136 137 func dropRune(text string) (res string, ok bool) { 138 r := []rune(text) 139 if len(r) == 0 { 140 return "", false 141 } 142 return string(r[1:]), true 143 } 144 145 func evalSQLSubstring(s string, startIdx, length int) (res string, err error) { 146 rs := []rune(s) 147 148 // According to s3 document, if startIdx < 1, it is set to 1. 149 if startIdx < 1 { 150 startIdx = 1 151 } 152 153 if startIdx > len(rs) { 154 startIdx = len(rs) + 1 155 } 156 157 // StartIdx is 1-based in the input 158 startIdx-- 159 endIdx := len(rs) 160 if length != -1 { 161 if length < 0 { 162 return "", errInvalidSubstringIndexLen 163 } 164 165 if length > (endIdx - startIdx) { 166 length = endIdx - startIdx 167 } 168 169 endIdx = startIdx + length 170 } 171 172 return string(rs[startIdx:endIdx]), nil 173 } 174 175 const ( 176 trimLeading = "LEADING" 177 trimTrailing = "TRAILING" 178 trimBoth = "BOTH" 179 ) 180 181 func evalSQLTrim(where *string, trimChars, text string) (result string, err error) { 182 cutSet := " " 183 if trimChars != "" { 184 cutSet = trimChars 185 } 186 187 trimFunc := strings.Trim 188 switch { 189 case where == nil: 190 case *where == trimBoth: 191 case *where == trimLeading: 192 trimFunc = strings.TrimLeft 193 case *where == trimTrailing: 194 trimFunc = strings.TrimRight 195 default: 196 return "", errInvalidTrimArg 197 } 198 199 return trimFunc(text, cutSet), nil 200 }