ServerBrowser: Add LIKE operator

This commit is contained in:
mkwcat
2024-02-06 17:51:52 -05:00
parent 7ddcd982bc
commit e934293d83
4 changed files with 82 additions and 7 deletions

View File

@@ -3,6 +3,7 @@ package filter
import (
"errors"
"regexp"
"strconv"
"strings"
)
@@ -55,7 +56,7 @@ func (this *expression) switchOther(node *TreeNode) {
func (this *expression) switchFunction(node *TreeNode) int64 {
val1 := node.Value.(*OperatorToken)
switch val1.Operator {
switch strings.ToLower(val1.Operator) {
case "=":
return this.evalEquals(node.Items())
case "==":
@@ -85,6 +86,9 @@ func (this *expression) switchFunction(node *TreeNode) int64 {
case "||":
return this.evalOr(node.Items())
case "like":
return this.evalLike(node.Items())
default:
panic("function not supported: " + val1.Operator)
}
@@ -288,6 +292,68 @@ func (this *expression) evalMathLessOrEqual(val1, val2 int64) int64 {
return 0
}
func (this *expression) evalLike(args []*TreeNode) int64 {
cnt := len(args)
switch {
case cnt < 2:
panic("operator missing arguments")
case cnt == 2:
return this.evalLikeSingle(args[0], args[1])
default:
panic("operator like does not support multiple arguments")
}
}
func (this *expression) evalLikeSingle(arg1, arg2 *TreeNode) int64 {
val1 := this.getString(arg1)
val2 := this.getString(arg2)
allowedCharacters := `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_%\`
regexString := "^"
// Convert SQL like pattern to regex
for i, c := range val2 {
if strings.IndexRune(allowedCharacters, c) < 0 {
panic("invalid character in like pattern: " + string(c))
}
if i != 0 && val2[i-1] == '\\' {
if c == '\\' {
regexString += "\\\\"
continue
}
regexString += string(c)
continue
}
switch c {
case '%':
regexString += ".*"
case '_':
regexString += "."
case '\\':
// Do nothing
default:
regexString += string(c)
}
}
regexString += "$"
if matched, err := regexp.MatchString(regexString, val1); err != nil {
panic(err)
} else if matched {
return 1
}
return 0
}
// Get a value from the context.
func (this *expression) getValue(token *IdentityToken) string {
return this.context[token.Name]

View File

@@ -4,6 +4,8 @@ package filter
import (
"errors"
"fmt"
"strings"
"unicode"
)
type stateFn func(*parser) stateFn
@@ -93,8 +95,15 @@ func (this *parser) parseCloseBracket() stateFn {
func (this *parser) AcceptOperator() bool {
scan := this.scan
state := scan.SaveState()
for _, op := range operatorList {
if scan.Prefix(op) {
if scan.Prefix(op) || scan.Prefix(strings.ToUpper(op)) {
p := scan.Peek()
if unicode.IsLetter(rune(op[0])) && (unicode.IsLetter(p) || unicode.IsNumber(p) || strings.IndexRune(charValidString, p) >= 0) {
// this is a prefix of a longer word
scan.LoadState(state)
continue
}
return true
}
}

View File

@@ -16,7 +16,7 @@ func (p Pos) Position() Pos {
const eof = -1
//Scanner, Iterates through a string.
// Scanner, Iterates through a string.
type Scanner struct {
input string
start Pos
@@ -52,7 +52,7 @@ func (this *Scanner) Commit() string {
return r1
}
//IsEOF check if the end of the current string has been reached.
// IsEOF check if the end of the current string has been reached.
func (this *Scanner) IsEOF() bool {
return int(this.pos) >= len(this.input)
}
@@ -65,7 +65,7 @@ func (this *Scanner) MoveStart(pos int) {
this.start = this.start + Pos(pos)
}
//Next returns the next rune in the input.
// Next returns the next rune in the input.
func (this *Scanner) Next() rune {
this.safebackup = true
if this.IsEOF() {

View File

@@ -153,7 +153,7 @@ type OperatorPrecedence [][]string
func (this OperatorPrecedence) Level(operator string) int {
for level, operators := range this {
for _, op := range operators {
if op == operator {
if op == strings.ToLower(operator) {
return 5 - level
}
}
@@ -177,7 +177,7 @@ var operators = OperatorPrecedence{
{"+", "-"},
{"==", "=", "!=", ">=", "<=", ">", "<"},
{"&&", "and"},
{"||", "or"},
{"||", "or", "like"},
}
var operatorList = operators.All()