add translation code for GroupBy "previous" arg

This commit is contained in:
Matt Jaffee 2018-09-28 14:30:18 -05:00
parent 61089981a2
commit f94cd8ae7d
No known key found for this signature in database
GPG key ID: 08A3DFFF987B11BF
2 changed files with 179 additions and 0 deletions

View file

@ -2006,6 +2006,8 @@ func (e *executor) translateCall(index string, idx *Index, c *pql.Call) error {
} else if c.Name == "Rows" {
fieldName = callArgString(c, "field")
rowKey = "previous"
} else if c.Name == "GroupBy" {
return errors.Wrap(e.translateGroupByCall(index, idx, c), "translating GroupBy")
} else {
colKey = "col"
fieldName = callArgString(c, "field")
@ -2067,6 +2069,61 @@ func (e *executor) translateCall(index string, idx *Index, c *pql.Call) error {
return nil
}
func (e *executor) translateGroupByCall(index string, idx *Index, c *pql.Call) error {
if c.Name != "GroupBy" {
panic("translateGroupByCall called with '" + c.Name + "'")
}
for _, child := range c.Children {
if err := e.translateCall(index, idx, child); err != nil {
return errors.Wrapf(err, "translating %s", child)
}
}
prev, ok := c.Args["previous"]
if !ok {
return nil // nothing else to be translated
}
previous, ok := prev.([]interface{})
if !ok {
return errors.Errorf("'previous' argument must be list, but got %T", prev)
}
if len(c.Children) != len(previous) {
return errors.Errorf("mismatched lengths for previous: %d and children: %d in %s", len(previous), len(c.Children), c)
}
fields := make([]*Field, len(c.Children))
for i, child := range c.Children {
fieldname := callArgString(child, "field")
field := idx.Field(fieldname)
if field == nil {
return errors.Wrapf(ErrFieldNotFound, "getting field '%s' from '%s'", fieldname, child)
}
fields[i] = field
}
for i, field := range fields {
prev := previous[i]
if field.keys() {
prevStr, ok := prev.(string)
if !ok {
return errors.New("prev value must be a string when field 'keys' option enabled")
}
ids, err := e.TranslateStore.TranslateRowsToUint64(index, field.Name(), []string{prevStr})
if err != nil {
return errors.Wrapf(err, "translating row key '%s'", prevStr)
}
previous[i] = ids[0]
} else {
if prevStr, ok := prev.(string); ok {
return errors.Errorf("got string row val '%s' in 'previous' for field %s which doesn't use string keys", prevStr, field.Name())
}
}
}
return nil
}
func (e *executor) translateResult(index string, idx *Index, call *pql.Call, result interface{}) (interface{}, error) {
switch result := result.(type) {
case *Row:

122
executor_internal_test.go Normal file
View file

@ -0,0 +1,122 @@
package pilosa
import (
"fmt"
"io/ioutil"
"strings"
"testing"
"github.com/pilosa/pilosa/pql"
)
func TestExecutor_TranslateGroupByCall(t *testing.T) {
e := &executor{
Holder: NewHolder(),
}
e.Holder.Path, _ = ioutil.TempDir("", "")
err := e.Holder.Open()
if err != nil {
t.Fatalf("opening holder: %v", err)
}
e.TranslateStore = e.Holder.translateFile
tf, _ := ioutil.TempFile("", "")
e.Holder.translateFile.Path = tf.Name()
err = e.Holder.translateFile.Open()
if err != nil {
t.Fatalf("opening translateFile: %v", err)
}
idx, err := e.Holder.CreateIndex("i", IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, erra := idx.CreateField("ak", OptFieldKeys())
_, errb := idx.CreateField("b")
_, errc := idx.CreateField("ck", OptFieldKeys())
if erra != nil || errb != nil || errc != nil {
t.Fatalf("creating fields %v, %v, %v", erra, errb, errc)
}
_, erra = e.TranslateStore.TranslateRowsToUint64("i", "ak", []string{"la"})
_, errb = e.TranslateStore.TranslateRowsToUint64("i", "ck", []string{"ha"})
if erra != nil || errb != nil {
t.Fatalf("translating rows %v, %v", erra, errb)
}
query, err := pql.ParseString(`GroupBy(Rows(field=ak), Rows(field=b), Rows(field=ck), previous=["la", 0, "ha"])`)
if err != nil {
t.Fatalf("parsing query: %v", err)
}
c := query.Calls[0]
err = e.translateGroupByCall("i", idx, c)
if err != nil {
t.Fatalf("translating call: %v", err)
}
if len(c.Args["previous"].([]interface{})) != 3 {
t.Fatalf("unexpected length for 'previous' arg %v", c.Args["previous"])
}
for i, v := range c.Args["previous"].([]interface{}) {
if !isInt(v) {
t.Fatalf("expected all items in previous to be ints, but '%v' at index %d is %[1]T", v, i)
}
}
errTests := []struct {
pql string
err string
}{
{
pql: `GroupBy(Rows(field=notfound), previous=1)`,
err: "'previous' argument must be list",
},
{
pql: `GroupBy(Rows(field=ak), previous=["la", 0])`,
err: "mismatched lengths",
},
{
pql: `GroupBy(Rows(field=ak), previous=[1])`,
err: "prev value must be a string",
},
{
pql: `GroupBy(Rows(field=notfound), previous=[1])`,
err: ErrFieldNotFound.Error(),
},
// TODO: an unknown key will actually allocate an id. this is probably bad.
// {
// pql: `GroupBy(Rows(field=ak), previous=["zoop"])`,
// err: "translating row key '",
// },
{
pql: `GroupBy(Rows(field=b), previous=["la"])`,
err: "which doesn't use string keys",
},
}
for i, test := range errTests {
t.Run(fmt.Sprintf("#%d_%s", i, test.err), func(t *testing.T) {
query, err := pql.ParseString(test.pql)
if err != nil {
t.Fatalf("parsing query: %v", err)
}
c := query.Calls[0]
err = e.translateGroupByCall("i", idx, c)
if err == nil {
t.Fatalf("expected error, but translated call is '%s", c)
}
if !strings.Contains(err.Error(), test.err) {
t.Fatalf("expected '%s', got '%v'", test.err, err)
}
})
}
}
func isInt(a interface{}) bool {
switch a.(type) {
case int, int64, uint, uint64:
return true
default:
return false
}
}