safing-portbase/database/query/parser.go

227 lines
4.6 KiB
Go

package query
import (
"errors"
"fmt"
"regexp"
"strings"
)
var (
operatorNames = map[string]uint8{
"==": Equals,
">": GreaterThan,
">=": GreaterThanOrEqual,
"<": LessThan,
"<=": LessThanOrEqual,
"f==": FloatEquals,
"f>": FloatGreaterThan,
"f>=": FloatGreaterThanOrEqual,
"f<": FloatLessThan,
"f<=": FloatLessThanOrEqual,
"sameas": SameAs,
"s==": SameAs,
"contains": Contains,
"co": Contains,
"startswith": StartsWith,
"sw": StartsWith,
"endswith": EndsWith,
"ew": EndsWith,
"in": In,
"matches": Matches,
"re": Matches,
"is": Is,
"exists": Exists,
"ex": Exists,
}
primaryNames = make(map[uint8]string)
)
func init() {
for opName, opID := range operatorNames {
name, ok := primaryNames[opID]
if ok {
if len(name) < len(opName) {
primaryNames[opID] = opName
}
} else {
primaryNames[opID] = opName
}
}
}
func getOpName(operator uint8) string {
name, ok := primaryNames[operator]
if ok {
return name
}
return "[unknown]"
}
type treeElement struct {
branches []*treeElement
text string
globalPosition int
}
var (
escapeReplacer = regexp.MustCompile("\\\\([^\\\\])")
)
// prepToken removes surrounding parenthesis and escape characters.
func prepToken(text string) string {
return escapeReplacer.ReplaceAllString(strings.Trim(text, "\""), "$1")
}
func extractSnippets(text string) (snippets []*treeElement, err error) {
skip := false
start := -1
inParenthesis := false
var pos int
var char rune
for pos, char = range text {
// skip
if skip {
skip = false
continue
}
if char == '\\' {
skip = true
}
// wait for parenthesis to be over
if inParenthesis {
if char == '"' {
snippets = append(snippets, &treeElement{
text: prepToken(text[start+1 : pos]),
globalPosition: start + 1,
})
start = -1
inParenthesis = false
}
continue
}
// handle segments
switch char {
case '\t', '\n', '\r', ' ', '(', ')':
if start >= 0 {
snippets = append(snippets, &treeElement{
text: prepToken(text[start:pos]),
globalPosition: start,
})
start = -1
}
default:
if start == -1 {
start = pos
}
}
// handle special segment characters
switch char {
case '(', ')':
snippets = append(snippets, &treeElement{
text: text[pos : pos+1],
globalPosition: pos,
})
case '"':
if start < pos {
return nil, fmt.Errorf("parenthesis ('\"') may not be within words, please escape with '\\' (position: %d)", pos+1)
}
inParenthesis = true
}
}
// add last
snippets = append(snippets, &treeElement{
text: prepToken(text[start : pos+1]),
globalPosition: start,
})
return snippets, nil
}
// ParseQuery parses a plaintext query. Special characters (that must be escaped with a '\') are: `\()` and any whitespaces.
func ParseQuery(query string) (*Query, error) {
snippets, err := extractSnippets(query)
if err != nil {
return nil, err
}
snippetsPos := 0
getElement := func() (*treeElement, error) {
if snippetsPos >= len(snippets) {
return nil, fmt.Errorf("unexpected end at position %d", len(query))
}
return snippets[snippetsPos], nil
}
// check for query word
queryWord, err := getElement()
if err != nil {
return nil, err
}
if queryWord.text != "query" {
return nil, errors.New("queries must start with \"query\"")
}
// get prefix
prefix, err := getElement()
if err != nil {
return nil, err
}
// check if no condition
if len(snippets) == 2 {
return New(prefix.text, nil)
}
// check for where word
whereWord, err := getElement()
if err != nil {
return nil, err
}
if whereWord.text != "where" {
return nil, errors.New("filtering queries must start conditions with \"where\"")
}
// parse conditions
condition, err := parseCondition(getElement)
if err != nil {
return nil, err
}
// check for additional tokens
// token := s.Scan()
// if token != scanner.EOF {
// return nil, fmt.Errorf("unexpected additional tokens at position %d", s.Position)
// }
return New(prefix.text, condition)
}
func parseCondition(getElement func() (*treeElement, error)) (Condition, error) {
first, err := getElement()
if err != nil {
return nil, err
}
switch first.text {
case "(":
return parseAndOr(getElement, true, nil, false)
// case ""
}
return nil, nil
}
func parseAndOr(getElement func() (*treeElement, error), expectBracket bool, preParsedCondition Condition, preParsedIsOr bool) (Condition, error) {
return nil, nil
}