featurebase/query/parser.go
Ben Johnson 3c832591df move index package to root package
This commit moves the index package to the root. Because pilosa
is an index at its core, it's redundant to have an index
subpackage. It also provides better naming such as `pilosa.Bitmap`
instead of `index.Bitmap`.
2015-08-31 16:54:53 -06:00

355 lines
8.9 KiB
Go

package query
import (
"errors"
"fmt"
"strconv"
"time"
log "github.com/cihub/seelog"
"github.com/davecgh/go-spew/spew"
"github.com/umbel/pilosa"
"github.com/umbel/pilosa/util"
)
var InvalidQueryError = errors.New("Invalid query format.")
type QueryParser struct {
tokens []Token
pos int
}
func (self *QueryParser) next() *Token {
log.Trace("QueryParser.next")
self.pos += 1
if self.pos > len(self.tokens) {
return nil
}
return &self.tokens[self.pos-1]
}
func (self *QueryParser) peek() *Token {
log.Trace("QueryParser.peek")
token := self.next()
self.backup()
return token
}
func (self *QueryParser) backup() {
log.Trace("QueryParser.backup")
self.pos -= 1
}
func (self *QueryParser) Parse() (query *Query, err error) {
log.Trace("QueryParser.Parse", query, err)
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("query: %v", r)
}
}
}()
var token *Token
id := util.RandomUUID()
query = &Query{Id: &id, Subqueries: make([]Query, 0), Args: make(map[string]interface{})}
token = self.next()
if token.Type != TYPE_FUNC {
return nil, fmt.Errorf("Expected function, found token %v.", token)
}
query.Operation = token.Text
token = self.next()
if token.Type != TYPE_LP {
return nil, fmt.Errorf("Expected '(', found token %v.", token)
}
const shortForm = "2006-01-02T15:04"
ArgLoop:
for {
token = self.next()
if token == nil {
return nil, fmt.Errorf("Unclosed parentheses!")
}
switch token.Type {
case TYPE_FUNC:
self.backup()
subquery, err := self.Parse()
if err != nil {
return nil, err
}
query.Subqueries = append(query.Subqueries, *subquery)
case TYPE_VALUE:
switch query.Operation {
case "range":
switch len(query.Args) {
case 0:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["id"] = i
case 1:
query.Args["frame"] = token.Text
case 2:
t, err := time.Parse(shortForm, token.Text)
if err != nil {
return nil, fmt.Errorf("Expecting integer DateTime (%v)", err)
}
query.Args["start"] = t
case 3:
t, err := time.Parse(shortForm, token.Text)
if err != nil {
return nil, fmt.Errorf("Expecting integer DateTime (%v)", err)
}
query.Args["end"] = t
}
case "mask":
switch len(query.Args) {
case 0:
query.Args["frame"] = token.Text
case 1:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["start"] = i
case 2:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["end"] = i
}
case "get":
switch len(query.Args) {
case 0:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["id"] = i
case 1:
query.Args["frame"] = token.Text
default:
return nil, fmt.Errorf("Unexpected argument! (%v)", token)
}
case "clear":
switch len(query.Args) {
case 0:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["id"] = i
case 1:
query.Args["frame"] = token.Text
case 2:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["filter"] = i
case 3:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["profile_id"] = i
default:
return nil, fmt.Errorf("Unexpected argument! (%v)", token)
}
case "set":
switch len(query.Args) {
case 0:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["id"] = i
case 1:
query.Args["frame"] = token.Text
case 2:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["filter"] = i
case 3:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["profile_id"] = i
default:
return nil, fmt.Errorf("Unexpected argument! (%v)", token)
}
case "top-n":
switch len(query.Args) {
case 0:
query.Args["frame"] = token.Text
case 1:
i, err := strconv.Atoi(token.Text)
if err != nil {
return nil, fmt.Errorf("Expecting integer! (%v)", err)
}
query.Args["n"] = i
}
case "all":
// do nothing
case "recall": //need pair based list of frag_id,handle
arg, ok := query.Args["stash"]
if !ok {
arg = &Stash{make([]CacheItem, 0), false}
query.Args["stash"] = arg
}
recallArgs := arg.(*Stash)
if !recallArgs.incomplete {
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting fragment id! (%v)", err)
}
recallArgs.Add(util.SUUID(i)) //constructs a new arg
} else {
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting handle! (%v)", err)
}
recallArgs.Assign(pilosa.BitmapHandle(i)) //sets the value of the last created arg
}
default:
spew.Dump("UNPROCESSED VALUE", token)
}
continue
case TYPE_COMMA:
continue
case TYPE_RP:
break ArgLoop
case TYPE_KEYWORD:
var value interface{}
keyword := token.Text
token = self.next()
if token == nil || token.Type != TYPE_EQUALS {
return nil, fmt.Errorf("Expecting equals sign!")
}
token = self.next()
if token == nil || token.Type != TYPE_VALUE {
return nil, fmt.Errorf("Expecting value!")
}
if keyword == "id" {
value, err = strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
} else if keyword == "n" {
value, err = strconv.Atoi(token.Text)
if err != nil {
return nil, fmt.Errorf("Expecting integer! (%v)", err)
}
} else {
value = token.Text
}
query.Args[keyword] = value
case TYPE_LB:
peeked := self.peek()
// we currently support 2 types of values in square brackets:
// ids <- list of integers (TYPE_VALUE)
// filters <- list of queries (TYPE_FUNC)
switch peeked.Type {
case TYPE_VALUE:
query.Args["ids"] = make([]uint64, 0)
for {
token = self.next()
if token == nil {
return nil, fmt.Errorf("Unclosed list!")
}
switch token.Type {
case TYPE_COMMA:
break
case TYPE_VALUE:
i, err := strconv.ParseUint(token.Text, 10, 64)
if err != nil {
return nil, fmt.Errorf("Expecting integer id! (%v)", err)
}
query.Args["ids"] = append(query.Args["ids"].([]uint64), i)
case TYPE_RB:
continue ArgLoop
default:
return nil, fmt.Errorf("Unexpected token! (%v)", token)
}
}
case TYPE_FUNC:
query.Args["filters"] = make([]Query, 0)
for {
token = self.next()
if token == nil {
return nil, fmt.Errorf("Unclosed list!")
}
switch token.Type {
case TYPE_COMMA:
break
case TYPE_FUNC:
self.backup()
filterquery, err := self.Parse()
if err != nil {
return nil, err
}
query.Args["filters"] = append(query.Args["filters"].([]Query), *filterquery)
case TYPE_RB:
continue ArgLoop
default:
return nil, fmt.Errorf("Unexpected token! (%v)", token)
}
}
}
case TYPE_RB:
//
default:
log.Warn(spew.Sdump("unexpected", token))
return nil, errors.New("BAD TOKEN")
}
}
if query.Operation == "get" && query.Args["frame"] == nil {
query.Args["frame"] = "general"
}
if len(query.Args) == 0 && len(query.Subqueries) == 0 {
if query.Operation == "count" {
return nil, fmt.Errorf("No Args Given")
}
if query.Operation == "intersect" {
return nil, fmt.Errorf("No Args Given")
}
if query.Operation == "union" {
return nil, fmt.Errorf("No Args Given")
}
if query.Operation == "difference" {
return nil, fmt.Errorf("No Args Given")
}
if query.Operation == "range" {
return nil, fmt.Errorf("No Args Given")
}
if query.Operation == "mask" {
return nil, fmt.Errorf("No Args Given")
}
if query.Operation == "stash" {
return nil, fmt.Errorf("No Args Given")
}
if query.Operation == "recall" {
return nil, fmt.Errorf("No Args Given")
}
}
return query, nil
}
func Parse(tokens []Token) (*Query, error) {
log.Trace("Parse", tokens)
parser := QueryParser{tokens, 0}
return parser.Parse()
}