mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
562 lines
14 KiB
Go
562 lines
14 KiB
Go
// Copyright 2017 Pilosa Corp.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pql
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Query represents a PQL query.
|
|
type Query struct {
|
|
Calls []*Call
|
|
|
|
callStack []*callStackElem
|
|
conditional []string
|
|
}
|
|
|
|
func (q *Query) startCall(name string) {
|
|
newCall := &Call{Name: name}
|
|
q.callStack = append(q.callStack, &callStackElem{call: newCall})
|
|
|
|
if len(q.callStack) == 1 {
|
|
q.Calls = append(q.Calls, newCall)
|
|
} else if prevElem := q.callStack[len(q.callStack)-2]; prevElem.lastField == "" {
|
|
prevElem.call.Children = append(prevElem.call.Children, newCall)
|
|
}
|
|
}
|
|
|
|
// endCall removes the last element from the call stack and returns the call.
|
|
func (q *Query) endCall() *Call {
|
|
elem := q.callStack[len(q.callStack)-1]
|
|
q.callStack[len(q.callStack)-1] = nil
|
|
q.callStack = q.callStack[:len(q.callStack)-1]
|
|
return elem.call
|
|
}
|
|
|
|
func (q *Query) lastCallStackElem() *callStackElem {
|
|
if len(q.callStack) == 0 {
|
|
return nil
|
|
}
|
|
return q.callStack[len(q.callStack)-1]
|
|
}
|
|
|
|
func (q *Query) addPosNum(key, value string) {
|
|
q.addField(key)
|
|
q.addNumVal(value)
|
|
}
|
|
|
|
func (q *Query) addPosStr(key, value string) {
|
|
q.addField(key)
|
|
q.addVal(value)
|
|
}
|
|
|
|
func (q *Query) startConditional() {
|
|
q.conditional = make([]string, 0)
|
|
elem := q.lastCallStackElem()
|
|
if elem.call.Args == nil {
|
|
elem.call.Args = make(map[string]interface{})
|
|
}
|
|
}
|
|
|
|
func (q *Query) condAdd(val string) {
|
|
q.conditional = append(q.conditional, val)
|
|
}
|
|
|
|
func (q *Query) endConditional() {
|
|
// do stuff
|
|
if len(q.conditional) != 5 {
|
|
panic(fmt.Sprintf("conditional of wrong length: %#v", q.conditional))
|
|
}
|
|
low, _ := strconv.ParseInt(q.conditional[0], 10, 64)
|
|
field := q.conditional[2]
|
|
high, _ := strconv.ParseInt(q.conditional[4], 10, 64)
|
|
|
|
if q.conditional[1] == "<" {
|
|
low++
|
|
}
|
|
if q.conditional[3] == "<" {
|
|
high--
|
|
}
|
|
|
|
elem := q.lastCallStackElem()
|
|
elem.call.Args[field] = &Condition{Op: BETWEEN, Value: []interface{}{low, high}}
|
|
|
|
q.conditional = nil
|
|
}
|
|
|
|
func (q *Query) addField(field string) {
|
|
elem := q.lastCallStackElem()
|
|
if elem == nil {
|
|
panic(fmt.Sprintf("addField called with '%s' while element is nil", field))
|
|
} else if elem.lastField != "" {
|
|
panic(fmt.Sprintf("addField called with '%s' while field is not empty, it's: %s", field, elem.lastField))
|
|
}
|
|
elem.lastField = field
|
|
if elem.call.Args == nil {
|
|
elem.call.Args = make(map[string]interface{})
|
|
}
|
|
}
|
|
|
|
// validateArgField ensures that field does not already
|
|
// exist as a key in the Args map before adding the new
|
|
// key/value.
|
|
func (q *Query) validateArgField(elem *callStackElem) {
|
|
if _, exists := elem.call.Args[elem.lastField]; exists {
|
|
panic(fmt.Sprintf("%s: %s", duplicateArgErrorMessage, elem.lastField))
|
|
}
|
|
}
|
|
|
|
func (q *Query) addVal(val interface{}) {
|
|
elem := q.lastCallStackElem()
|
|
if elem == nil || elem.lastField == "" {
|
|
panic(fmt.Sprintf("addVal called with '%s' when lastField is empty", val))
|
|
}
|
|
if elem.inList {
|
|
list := elem.call.Args[elem.lastField].([]interface{})
|
|
elem.call.Args[elem.lastField] = append(list, val)
|
|
return
|
|
}
|
|
if elem.lastCond != ILLEGAL {
|
|
q.validateArgField(elem) // case 1
|
|
elem.call.Args[elem.lastField] = &Condition{
|
|
Op: elem.lastCond,
|
|
Value: val,
|
|
}
|
|
} else {
|
|
q.validateArgField(elem) // case 2
|
|
elem.call.Args[elem.lastField] = val
|
|
}
|
|
elem.lastField = ""
|
|
elem.lastCond = ILLEGAL
|
|
}
|
|
|
|
func (q *Query) addNumVal(val string) {
|
|
elem := q.lastCallStackElem()
|
|
if elem == nil || elem.lastField == "" {
|
|
panic(fmt.Sprintf("addIntVal called with '%s' when lastField is empty", val))
|
|
}
|
|
var ival interface{}
|
|
var err error
|
|
if strings.Contains(val, ".") {
|
|
ival, err = strconv.ParseFloat(val, 64)
|
|
} else {
|
|
ival, err = strconv.ParseInt(val, 10, 64)
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if elem.inList {
|
|
if elem.lastCond != ILLEGAL {
|
|
list := elem.call.Args[elem.lastField].(*Condition).Value.([]interface{})
|
|
elem.call.Args[elem.lastField] = &Condition{
|
|
Op: elem.lastCond,
|
|
Value: append(list, ival),
|
|
}
|
|
} else {
|
|
list := elem.call.Args[elem.lastField].([]interface{})
|
|
elem.call.Args[elem.lastField] = append(list, ival)
|
|
}
|
|
return
|
|
} else if elem.lastCond != ILLEGAL {
|
|
q.validateArgField(elem) // case 3
|
|
elem.call.Args[elem.lastField] = &Condition{
|
|
Op: elem.lastCond,
|
|
Value: ival,
|
|
}
|
|
} else {
|
|
q.validateArgField(elem) // case 4
|
|
elem.call.Args[elem.lastField] = ival
|
|
}
|
|
elem.lastField = ""
|
|
elem.lastCond = ILLEGAL
|
|
}
|
|
|
|
func (q *Query) startList() {
|
|
elem := q.lastCallStackElem()
|
|
q.validateArgField(elem) // case 5
|
|
if elem.lastCond != ILLEGAL {
|
|
elem.call.Args[elem.lastField] = &Condition{
|
|
Op: elem.lastCond,
|
|
Value: make([]interface{}, 0),
|
|
}
|
|
} else {
|
|
elem.call.Args[elem.lastField] = make([]interface{}, 0)
|
|
}
|
|
elem.inList = true
|
|
}
|
|
|
|
func (q *Query) endList() {
|
|
elem := q.lastCallStackElem()
|
|
elem.inList = false
|
|
elem.lastField = ""
|
|
elem.lastCond = ILLEGAL
|
|
}
|
|
|
|
func (q *Query) addGT() {
|
|
q.lastCallStackElem().lastCond = GT
|
|
}
|
|
func (q *Query) addLT() {
|
|
q.lastCallStackElem().lastCond = LT
|
|
}
|
|
func (q *Query) addGTE() {
|
|
q.lastCallStackElem().lastCond = GTE
|
|
}
|
|
func (q *Query) addLTE() {
|
|
q.lastCallStackElem().lastCond = LTE
|
|
}
|
|
func (q *Query) addEQ() {
|
|
q.lastCallStackElem().lastCond = EQ
|
|
}
|
|
func (q *Query) addNEQ() {
|
|
q.lastCallStackElem().lastCond = NEQ
|
|
}
|
|
func (q *Query) addBTWN() {
|
|
q.lastCallStackElem().lastCond = BETWEEN
|
|
}
|
|
|
|
// WriteCallN returns the number of mutating calls.
|
|
func (q *Query) WriteCallN() int {
|
|
var n int
|
|
for _, call := range q.Calls {
|
|
switch call.Name {
|
|
case "Set", "Clear", "SetRowAttrs", "SetColumnAttrs":
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// String returns a string representation of the query.
|
|
func (q *Query) String() string {
|
|
a := make([]string, len(q.Calls))
|
|
for i, call := range q.Calls {
|
|
a[i] = call.String()
|
|
}
|
|
return strings.Join(a, "\n")
|
|
}
|
|
|
|
type callStackElem struct {
|
|
call *Call
|
|
lastField string
|
|
lastCond Token
|
|
inList bool
|
|
}
|
|
|
|
// Call represents a function call in the AST.
|
|
type Call struct {
|
|
Name string
|
|
Args map[string]interface{}
|
|
Children []*Call
|
|
}
|
|
|
|
// FieldArg determines which key-value pair contains the field and rowID,
|
|
// in the case of arguments like Set(colID, field=rowID).
|
|
// Returns the field as a string if present, or an error if not.
|
|
func (c *Call) FieldArg() (string, error) {
|
|
for arg := range c.Args {
|
|
if !IsReservedArg(arg) {
|
|
return arg, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("no field argument specified")
|
|
}
|
|
|
|
func IsReservedArg(name string) bool {
|
|
if strings.HasPrefix(name, "_") {
|
|
return true
|
|
}
|
|
switch name {
|
|
case "from", "to":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// BoolArg is for reading the value at key from call.Args as a bool. If the
|
|
// key is not in Call.Args, the value of the returned bool will be false, and
|
|
// the error will be nil. The value is assumed to be a bool. An error is
|
|
// returned if the value is not a bool.
|
|
func (c *Call) BoolArg(key string) (bool, bool, error) {
|
|
val, ok := c.Args[key]
|
|
if !ok {
|
|
return false, false, nil
|
|
}
|
|
switch tval := val.(type) {
|
|
case bool:
|
|
return tval, true, nil
|
|
default:
|
|
return false, true, fmt.Errorf("could not convert %v of type %T to bool in Call.BoolArg", tval, tval)
|
|
}
|
|
}
|
|
|
|
// UintArg is for reading the value at key from call.Args as a uint64. If the
|
|
// key is not in Call.Args, the value of the returned bool will be false, and
|
|
// the error will be nil. The value is assumed to be a uint64 or an int64 and
|
|
// then cast to a uint64. An error is returned if the value is not an int64 or
|
|
// uint64.
|
|
func (c *Call) UintArg(key string) (uint64, bool, error) {
|
|
val, ok := c.Args[key]
|
|
if !ok {
|
|
return 0, false, nil
|
|
}
|
|
switch tval := val.(type) {
|
|
case int64:
|
|
if tval < 0 {
|
|
return 0, true, fmt.Errorf("value for '%s' must be positive, but got %v", key, tval)
|
|
}
|
|
return uint64(tval), true, nil
|
|
case uint64:
|
|
return tval, true, nil
|
|
default:
|
|
return 0, true, fmt.Errorf("could not convert %v of type %T to uint64 in Call.UintArg", tval, tval)
|
|
}
|
|
}
|
|
|
|
// IntArg is for reading the value at key from call.Args as an int64. If the
|
|
// key is not in Call.Args, the value of the returned bool will be false, and
|
|
// the error will be nil. The value is assumed to be a unt64 or an int64 and
|
|
// then cast to an int64. An error is returned if the value is not an int64 or
|
|
// uint64.
|
|
func (c *Call) IntArg(key string) (int64, bool, error) {
|
|
val, ok := c.Args[key]
|
|
if !ok {
|
|
return 0, false, nil
|
|
}
|
|
switch tval := val.(type) {
|
|
case int64:
|
|
return tval, true, nil
|
|
case uint64:
|
|
return int64(tval), true, nil
|
|
default:
|
|
return 0, true, fmt.Errorf("could not convert %v of type %T to int64 in Call.IntArg", tval, tval)
|
|
}
|
|
}
|
|
|
|
// UintSliceArg reads the value at key from call.Args as a slice of uint64. If
|
|
// the key is not in Call.Args, the value of the returned bool will be false,
|
|
// and the error will be nil. If the value is a slice of int64 it will convert
|
|
// it to []uint64. Otherwise, if it is not a []uint64 it will return an error.
|
|
func (c *Call) UintSliceArg(key string) ([]uint64, bool, error) {
|
|
val, ok := c.Args[key]
|
|
if !ok {
|
|
return nil, false, nil
|
|
}
|
|
|
|
switch tval := val.(type) {
|
|
case []uint64:
|
|
return tval, true, nil
|
|
case []int64:
|
|
ret := make([]uint64, len(tval))
|
|
for i, v := range tval {
|
|
ret[i] = uint64(v)
|
|
}
|
|
return ret, true, nil
|
|
default:
|
|
return nil, true, fmt.Errorf("unexpected type %T in UintSliceArg, val %v", tval, tval)
|
|
}
|
|
}
|
|
|
|
// CallArg is for reading the value at key from call.Args as a Call. If the
|
|
// key is not in Call.Args, the value of the returned value will be nil, and
|
|
// the error will be nil. An error is returned if the value is not a Call.
|
|
func (c *Call) CallArg(key string) (*Call, bool, error) {
|
|
val, ok := c.Args[key]
|
|
if !ok {
|
|
return nil, false, nil
|
|
}
|
|
switch tval := val.(type) {
|
|
case *Call:
|
|
return tval, true, nil
|
|
default:
|
|
return nil, true, fmt.Errorf("could not convert %v of type %T to Call in Call.CallArg", tval, tval)
|
|
}
|
|
}
|
|
|
|
// keys returns a list of argument keys in sorted order.
|
|
func (c *Call) keys() []string {
|
|
a := make([]string, 0, len(c.Args))
|
|
for k := range c.Args {
|
|
a = append(a, k)
|
|
}
|
|
sort.Strings(a)
|
|
return a
|
|
}
|
|
|
|
// Clone returns a copy of c.
|
|
func (c *Call) Clone() *Call {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
other := &Call{
|
|
Name: c.Name,
|
|
Args: CopyArgs(c.Args),
|
|
}
|
|
if c.Children != nil {
|
|
other.Children = make([]*Call, len(c.Children))
|
|
for i := range c.Children {
|
|
other.Children[i] = c.Children[i].Clone()
|
|
}
|
|
}
|
|
return other
|
|
}
|
|
|
|
// String returns the string representation of the call.
|
|
func (c *Call) String() string {
|
|
var buf bytes.Buffer
|
|
|
|
// Write name.
|
|
if c.Name != "" {
|
|
buf.WriteString(c.Name)
|
|
} else {
|
|
buf.WriteString("!UNNAMED")
|
|
}
|
|
|
|
// Write opening.
|
|
buf.WriteByte('(')
|
|
|
|
// Write child list.
|
|
for i, child := range c.Children {
|
|
if i > 0 {
|
|
buf.WriteString(", ")
|
|
}
|
|
buf.WriteString(child.String())
|
|
}
|
|
|
|
// Separate children and args, if necessary.
|
|
if len(c.Children) > 0 && len(c.Args) > 0 {
|
|
buf.WriteString(", ")
|
|
}
|
|
|
|
// Write arguments in key order.
|
|
for i, key := range c.keys() {
|
|
if i > 0 {
|
|
buf.WriteString(", ")
|
|
}
|
|
// If the Arg value is a Condition, then don't include
|
|
// the equal sign in the string representation.
|
|
switch v := c.Args[key].(type) {
|
|
case *Condition:
|
|
fmt.Fprintf(&buf, "%v %s", key, v.String())
|
|
default:
|
|
fmt.Fprintf(&buf, "%v=%s", key, formatValue(v))
|
|
}
|
|
}
|
|
|
|
// Write closing.
|
|
buf.WriteByte(')')
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// HasConditionArg returns true if any arg is a conditional.
|
|
func (c *Call) HasConditionArg() bool {
|
|
for _, v := range c.Args {
|
|
if _, ok := v.(*Condition); ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Condition represents an operation & value.
|
|
// When used in an argument map it represents a binary expression.
|
|
type Condition struct {
|
|
Op Token
|
|
Value interface{}
|
|
}
|
|
|
|
// String returns the string representation of the condition.
|
|
func (cond *Condition) String() string {
|
|
return fmt.Sprintf("%s %s", cond.Op.String(), formatValue(cond.Value))
|
|
}
|
|
|
|
// IntSliceValue reads cond.Value as a slice of uint64.
|
|
// If the value is a slice of uint64 it will convert
|
|
// it to []int64. Otherwise, if it is not a []int64 it will return an error.
|
|
func (cond *Condition) IntSliceValue() ([]int64, error) {
|
|
val := cond.Value
|
|
|
|
switch tval := val.(type) {
|
|
case []interface{}:
|
|
ret := make([]int64, len(tval))
|
|
for i, v := range tval {
|
|
switch tv := v.(type) {
|
|
case int64:
|
|
ret[i] = tv
|
|
case uint64:
|
|
ret[i] = int64(tv)
|
|
default:
|
|
return nil, fmt.Errorf("unexpected value type %T in IntSliceValue, val %v", tv, tv)
|
|
}
|
|
}
|
|
return ret, nil
|
|
default:
|
|
return nil, fmt.Errorf("unexpected type %T in IntSliceValue, val %v", tval, tval)
|
|
}
|
|
}
|
|
|
|
func formatValue(v interface{}) string {
|
|
switch v := v.(type) {
|
|
case string:
|
|
return fmt.Sprintf("%q", v)
|
|
case []interface{}:
|
|
return joinInterfaceSlice(v)
|
|
case []uint64:
|
|
return joinUint64Slice(v)
|
|
case time.Time:
|
|
return fmt.Sprintf("\"%s\"", v.Format(timeFormat))
|
|
case *Condition:
|
|
return v.String()
|
|
default:
|
|
return fmt.Sprintf("%v", v)
|
|
}
|
|
}
|
|
|
|
// CopyArgs returns a copy of m.
|
|
func CopyArgs(m map[string]interface{}) map[string]interface{} {
|
|
other := make(map[string]interface{}, len(m))
|
|
for k, v := range m {
|
|
other[k] = v
|
|
}
|
|
return other
|
|
}
|
|
|
|
func joinInterfaceSlice(a []interface{}) string {
|
|
other := make([]string, len(a))
|
|
for i := range a {
|
|
switch v := a[i].(type) {
|
|
case string:
|
|
other[i] = fmt.Sprintf("%q", v)
|
|
default:
|
|
other[i] = fmt.Sprintf("%v", v)
|
|
}
|
|
}
|
|
return "[" + strings.Join(other, ",") + "]"
|
|
}
|
|
|
|
func joinUint64Slice(a []uint64) string {
|
|
other := make([]string, len(a))
|
|
for i := range a {
|
|
other[i] = strconv.FormatUint(a[i], 10)
|
|
}
|
|
return "[" + strings.Join(other, ",") + "]"
|
|
}
|