// Copyright (c) 2015-2021 MinIO, Inc. // // This file is part of MinIO Object Storage stack // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package sql import ( "errors" "strings" ) var ( errMalformedEscapeSequence = errors.New("Malformed escape sequence in LIKE clause") errInvalidTrimArg = errors.New("Trim argument is invalid - this should not happen") errInvalidSubstringIndexLen = errors.New("Substring start index or length falls outside the string") ) const ( percent rune = '%' underscore rune = '_' runeZero rune = 0 ) func evalSQLLike(text, pattern string, escape rune) (match bool, err error) { s := []rune{} prev := runeZero hasLeadingPercent := false patLen := len([]rune(pattern)) for i, r := range pattern { if i > 0 && prev == escape { switch r { case percent, escape, underscore: s = append(s, r) prev = r if r == escape { prev = runeZero } default: return false, errMalformedEscapeSequence } continue } prev = r var ok bool switch r { case percent: if len(s) == 0 { hasLeadingPercent = true continue } text, ok = matcher(text, string(s), hasLeadingPercent) if !ok { return false, nil } hasLeadingPercent = true s = []rune{} if i == patLen-1 { // Last pattern character is a %, so // we are done. return true, nil } case underscore: if len(s) == 0 { text, ok = dropRune(text) if !ok { return false, nil } continue } text, ok = matcher(text, string(s), hasLeadingPercent) if !ok { return false, nil } hasLeadingPercent = false text, ok = dropRune(text) if !ok { return false, nil } s = []rune{} case escape: if i == patLen-1 { return false, errMalformedEscapeSequence } // Otherwise do nothing. default: s = append(s, r) } } if hasLeadingPercent { return strings.HasSuffix(text, string(s)), nil } return string(s) == text, nil } // matcher - Finds `pat` in `text`, and returns the part remainder of // `text`, after the match. If leadingPercent is false, `pat` must be // the prefix of `text`, otherwise it must be a substring. func matcher(text, pat string, leadingPercent bool) (res string, match bool) { if !leadingPercent { res = strings.TrimPrefix(text, pat) if len(text) == len(res) { return "", false } } else { parts := strings.SplitN(text, pat, 2) if len(parts) == 1 { return "", false } res = parts[1] } return res, true } func dropRune(text string) (res string, ok bool) { r := []rune(text) if len(r) == 0 { return "", false } return string(r[1:]), true } func evalSQLSubstring(s string, startIdx, length int) (res string, err error) { rs := []rune(s) // According to s3 document, if startIdx < 1, it is set to 1. if startIdx < 1 { startIdx = 1 } if startIdx > len(rs) { startIdx = len(rs) + 1 } // StartIdx is 1-based in the input startIdx-- endIdx := len(rs) if length != -1 { if length < 0 { return "", errInvalidSubstringIndexLen } if length > (endIdx - startIdx) { length = endIdx - startIdx } endIdx = startIdx + length } return string(rs[startIdx:endIdx]), nil } const ( trimLeading = "LEADING" trimTrailing = "TRAILING" trimBoth = "BOTH" ) func evalSQLTrim(where *string, trimChars, text string) (result string, err error) { cutSet := " " if trimChars != "" { cutSet = trimChars } trimFunc := strings.Trim switch { case where == nil: case *where == trimBoth: case *where == trimLeading: trimFunc = strings.TrimLeft case *where == trimTrailing: trimFunc = strings.TrimRight default: return "", errInvalidTrimArg } return trimFunc(text, cutSet), nil }