featurebase/pql/ast.go
Ben Johnson 569bafdc7d add bulk call support to the query endpoint
This commit changes the `pql.Query` so that it can accept one or more
top-level calls instead of only one.

The query request format change because a query with a single call is
still valid. However, the result format now returns a `results` field
that has one result for each top-level call. The `profiles` field is
still the same, however, it combines all profiles from all bitmap
responses into one return so that there's not duplicate attributes.

Fixes #59
2016-03-08 14:34:37 -07:00

365 lines
8.8 KiB
Go

package pql
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
)
// Query represents a PQL query.
type Query struct {
Calls Calls
}
// 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")
}
// Node represents any node in the AST.
type Node interface {
node()
String() string
}
func (*Bitmap) node() {}
func (*ClearBit) node() {}
func (*Count) node() {}
func (*Difference) node() {}
func (*Intersect) node() {}
func (*Profile) node() {}
func (*Range) node() {}
func (*SetBit) node() {}
func (*SetBitmapAttrs) node() {}
func (*SetProfileAttrs) node() {}
func (*TopN) node() {}
func (*Union) node() {}
// Call represents a function call in the AST.
type Call interface {
Node
call()
}
func (*Bitmap) call() {}
func (*ClearBit) call() {}
func (*Count) call() {}
func (*Difference) call() {}
func (*Intersect) call() {}
func (*Profile) call() {}
func (*Range) call() {}
func (*SetBit) call() {}
func (*SetBitmapAttrs) call() {}
func (*SetProfileAttrs) call() {}
func (*TopN) call() {}
func (*Union) call() {}
// Calls represents a list of calls.
type Calls []Call
// String returns a string representation of the calls as a comma-delimited list.
func (a Calls) String() string {
args := make([]string, len(a))
for i, c := range a {
args[i] = c.String()
}
return strings.Join(args, ", ")
}
// BitmapCall represents a function call that returns a bitmap.
type BitmapCall interface {
Call
bitmapCall()
}
// BitmapCalls represents a list of bitmap calls.
type BitmapCalls []BitmapCall
// String returns a string representation of the calls as a comma-delimited list.
func (a BitmapCalls) String() string {
args := make([]string, len(a))
for i, c := range a {
args[i] = c.String()
}
return strings.Join(args, ", ")
}
func (*Difference) bitmapCall() {}
func (*Bitmap) bitmapCall() {}
func (*Intersect) bitmapCall() {}
func (*Range) bitmapCall() {}
func (*Union) bitmapCall() {}
// Bitmap represents a Bitmap() function call.
type Bitmap struct {
ID uint64
Frame string
}
// String returns the string representation of the call.
func (c *Bitmap) String() string {
args := make([]string, 0, 2)
if c.ID != 0 {
args = append(args, fmt.Sprintf("id=%d", c.ID))
}
if c.Frame != "" {
args = append(args, fmt.Sprintf("frame=%s", c.Frame))
}
return fmt.Sprintf("Bitmap(%s)", strings.Join(args, ", "))
}
// ClearBit represents a ClearBit() function call.
type ClearBit struct {
ID uint64
Frame string
ProfileID uint64
}
// String returns the string representation of the call.
func (c *ClearBit) String() string {
args := make([]string, 0, 4)
args = append(args, fmt.Sprintf("id=%d", c.ID))
if c.Frame != "" {
args = append(args, fmt.Sprintf("frame=%s", c.Frame))
}
if c.ProfileID != 0 {
args = append(args, fmt.Sprintf("profileID=%d", c.ProfileID))
}
return fmt.Sprintf("ClearBit(%s)", strings.Join(args, ", "))
}
// Count represents a count() function call.
type Count struct {
Input BitmapCall
}
// String returns the string representation of the call.
func (c *Count) String() string {
return fmt.Sprintf("Count(%s)", c.Input.String())
}
// Difference represents an difference() function call.
type Difference struct {
Inputs BitmapCalls
}
// String returns the string representation of the call.
func (c *Difference) String() string {
return fmt.Sprintf("Difference(%s)", c.Inputs.String())
}
// Intersect represents an intersect() function call.
type Intersect struct {
Inputs BitmapCalls
}
// String returns the string representation of the call.
func (c *Intersect) String() string {
return fmt.Sprintf("Intersect(%s)", c.Inputs.String())
}
// Profile represents a Profile() function call.
type Profile struct {
ID uint64
}
// String returns the string representation of the call.
func (c *Profile) String() string {
args := make([]string, 0, 1)
args = append(args, fmt.Sprintf("id=%d", c.ID))
return fmt.Sprintf("Profile(%s)", strings.Join(args, ", "))
}
// Range represents a range() function call.
type Range struct {
ID uint64
Frame string
StartTime time.Time
EndTime time.Time
}
// String returns the string representation of the call.
func (c *Range) String() string {
args := make([]string, 0, 2)
if c.ID != 0 {
args = append(args, fmt.Sprintf("id=%d", c.ID))
}
if c.Frame != "" {
args = append(args, fmt.Sprintf("frame=%s", c.Frame))
}
if !c.StartTime.IsZero() {
args = append(args, fmt.Sprintf("start=%s", c.StartTime.Format(TimeFormat)))
}
if !c.EndTime.IsZero() {
args = append(args, fmt.Sprintf("end=%s", c.EndTime.Format(TimeFormat)))
}
return fmt.Sprintf("Range(%s)", strings.Join(args, ", "))
}
// SetBit represents a SetBit() function call.
type SetBit struct {
ID uint64
Frame string
ProfileID uint64
}
// String returns the string representation of the call.
func (c *SetBit) String() string {
args := make([]string, 0, 2)
args = append(args, fmt.Sprintf("id=%d", c.ID))
if c.Frame != "" {
args = append(args, fmt.Sprintf("frame=%s", c.Frame))
}
if c.ProfileID != 0 {
args = append(args, fmt.Sprintf("profileID=%d", c.ProfileID))
}
return fmt.Sprintf("SetBit(%s)", strings.Join(args, ", "))
}
// SetBitmapAttrs represents a SetBitmapAttrs() function call.
type SetBitmapAttrs struct {
ID uint64
Frame string
Attrs map[string]interface{}
}
// String returns the string representation of the call.
func (c *SetBitmapAttrs) String() string {
args := make([]string, 0, 2)
args = append(args, fmt.Sprintf("id=%d", c.ID))
if c.Frame != "" {
args = append(args, fmt.Sprintf("frame=%s", c.Frame))
}
// Sort keys.
keys := make([]string, 0, len(c.Attrs))
for k := range c.Attrs {
keys = append(keys, k)
}
sort.Strings(keys)
// Write key/value pairs.
for _, k := range keys {
if c.Attrs[k] == nil {
args = append(args, fmt.Sprintf("%s=null", k))
continue
}
switch v := c.Attrs[k].(type) {
case string:
args = append(args, fmt.Sprintf("%s=\"%s\"", k, v))
default:
args = append(args, fmt.Sprintf("%s=%v", k, v))
}
}
return fmt.Sprintf("SetBitmapAttrs(%s)", strings.Join(args, ", "))
}
// SetProfileAttrs represents a SetProfileAttrs() function call.
type SetProfileAttrs struct {
ID uint64
Attrs map[string]interface{}
}
// String returns the string representation of the call.
func (c *SetProfileAttrs) String() string {
args := make([]string, 0, 2)
args = append(args, fmt.Sprintf("id=%d", c.ID))
// Sort keys.
keys := make([]string, 0, len(c.Attrs))
for k := range c.Attrs {
keys = append(keys, k)
}
sort.Strings(keys)
// Write key/value pairs.
for _, k := range keys {
if c.Attrs[k] == nil {
args = append(args, fmt.Sprintf("%s=null", k))
continue
}
switch v := c.Attrs[k].(type) {
case string:
args = append(args, fmt.Sprintf("%s=\"%s\"", k, v))
default:
args = append(args, fmt.Sprintf("%s=%v", k, v))
}
}
return fmt.Sprintf("SetProfileAttrs(%s)", strings.Join(args, ", "))
}
// TopN represents a TopN() function call.
type TopN struct {
Frame string
// Maximum number of results to return.
N int
// Bitmap to use for intersection while computing top results.
// Original bitmap counts are used if no Src is provided.
Src BitmapCall
// Specific bitmaps to retrieve.
BitmapIDs []uint64
// Field name and values to filter on.
Field string
Filters []interface{}
}
// String returns the string representation of the call.
func (c *TopN) String() string {
args := make([]string, 0, 2)
if c.Src != nil {
args = append(args, c.Src.String())
}
if c.Frame != "" {
args = append(args, fmt.Sprintf("frame=%s", c.Frame))
}
if c.N > 0 {
args = append(args, fmt.Sprintf("n=%d", c.N))
}
if len(c.BitmapIDs) > 0 {
strs := make([]string, len(c.BitmapIDs))
for i := range c.BitmapIDs {
strs[i] = strconv.FormatUint(c.BitmapIDs[i], 10)
}
args = append(args, fmt.Sprintf("ids=[%s]", strings.Join(strs, ",")))
}
if c.Field != "" {
args = append(args, fmt.Sprintf("field=%q", c.Field))
}
if len(c.Filters) > 0 {
filters := make([]string, 0, len(c.Filters))
for i := range c.Filters {
switch filter := c.Filters[i].(type) {
case string:
filters = append(filters, fmt.Sprintf("%q", filter))
default:
filters = append(filters, fmt.Sprintf("%v", filter))
}
}
args = append(args, fmt.Sprintf("[%s]", strings.Join(filters, ",")))
}
return fmt.Sprintf("TopN(%s)", strings.Join(args, ", "))
}
// Union represents a union() function call.
type Union struct {
Inputs BitmapCalls
}
// String returns the string representation of the call.
func (c *Union) String() string {
return fmt.Sprintf("Union(%s)", c.Inputs.String())
}