2021-04-18 15:41:13 -04:00
|
|
|
// 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 <http://www.gnu.org/licenses/>.
|
2019-01-28 20:59:48 -05:00
|
|
|
|
|
|
|
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) {
|
2019-07-05 12:43:10 -04:00
|
|
|
rs := []rune(s)
|
|
|
|
|
|
|
|
// According to s3 document, if startIdx < 1, it is set to 1.
|
|
|
|
if startIdx < 1 {
|
|
|
|
startIdx = 1
|
2019-01-28 20:59:48 -05:00
|
|
|
}
|
2019-07-05 12:43:10 -04:00
|
|
|
|
|
|
|
if startIdx > len(rs) {
|
|
|
|
startIdx = len(rs) + 1
|
|
|
|
}
|
|
|
|
|
2019-01-28 20:59:48 -05:00
|
|
|
// StartIdx is 1-based in the input
|
|
|
|
startIdx--
|
|
|
|
endIdx := len(rs)
|
|
|
|
if length != -1 {
|
2019-07-05 12:43:10 -04:00
|
|
|
if length < 0 {
|
2019-01-28 20:59:48 -05:00
|
|
|
return "", errInvalidSubstringIndexLen
|
|
|
|
}
|
2019-07-05 12:43:10 -04:00
|
|
|
|
|
|
|
if length > (endIdx - startIdx) {
|
|
|
|
length = endIdx - startIdx
|
|
|
|
}
|
|
|
|
|
2019-01-28 20:59:48 -05:00
|
|
|
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
|
|
|
|
}
|