diff --git a/executor.go b/executor.go index 56cdcb44e..788e8fe5e 100644 --- a/executor.go +++ b/executor.go @@ -100,8 +100,6 @@ func (e *Executor) executeCall(ctx context.Context, db string, c *pql.Call, slic return e.executeClearBit(ctx, db, c, opt) case "Count": return e.executeCount(ctx, db, c, slices, opt) - case "Profile": - return e.executeProfile(ctx, db, c, opt) case "SetBit": return e.executeSetBit(ctx, db, c, opt) case "SetBitmapAttrs": @@ -156,21 +154,32 @@ func (e *Executor) executeBitmapCall(ctx context.Context, db string, c *pql.Call return nil, err } - // Attach bitmap attributes for Bitmap() calls. + // Attach attributes for Bitmap() calls. + // If the column label is used then return profile attributes. + // If the row label is used then return bitmap attributes. bm, _ := other.(*Bitmap) if c.Name == "Bitmap" { - frame, _ := c.Args["frame"].(string) - - fr := e.Index.Frame(db, frame) - if fr != nil { - rowLabel := fr.RowLabel() - rowID, _ := c.Args[rowLabel].(uint64) - - attrs, err := fr.BitmapAttrStore().Attrs(rowID) - if err != nil { - return nil, err + d := e.Index.DB(db) + if d != nil { + columnLabel := d.ColumnLabel() + if columnID, ok := c.Args[columnLabel].(uint64); ok { + attrs, err := d.ProfileAttrStore().Attrs(columnID) + if err != nil { + return nil, err + } + bm.Attrs = attrs + } else { + frame, _ := c.Args["frame"].(string) + if fr := d.Frame(frame); fr != nil { + rowLabel := fr.RowLabel() + rowID, _ := c.Args[rowLabel].(uint64) + attrs, err := fr.BitmapAttrStore().Attrs(rowID) + if err != nil { + return nil, err + } + bm.Attrs = attrs + } } - bm.Attrs = attrs } } @@ -330,27 +339,47 @@ func (e *Executor) executeDifferenceSlice(ctx context.Context, db string, c *pql } func (e *Executor) executeBitmapSlice(ctx context.Context, db string, c *pql.Call, slice uint64) (*Bitmap, error) { + // Fetch column label from database. + d := e.Index.DB(db) + if d == nil { + return nil, ErrDatabaseNotFound + } + columnLabel := d.ColumnLabel() + + // Fetch frame & row label based on argument. frame, _ := c.Args["frame"].(string) if frame == "" { frame = DefaultFrame } - f := e.Index.Frame(db, frame) if f == nil { return nil, ErrFrameNotFound } rowLabel := f.RowLabel() - rowID, ok := c.Args[rowLabel].(uint64) - if !ok { - return nil, fmt.Errorf("Bitmap() field required: %s", rowLabel) + // Return an error if both the row and column label are specified. + rowID, rowOK := c.Args[rowLabel].(uint64) + columnID, columnOK := c.Args[columnLabel].(uint64) + if rowOK && columnOK { + return nil, fmt.Errorf("Bitmap() cannot specify both %s and %s values", rowLabel, columnLabel) + } else if !rowOK && !columnOK { + return nil, fmt.Errorf("Bitmap() must specify either %s or %s values", rowLabel, columnLabel) } - frag := e.Index.Fragment(db, frame, ViewStandard, slice) + // Determine row or column orientation. + view, id := ViewStandard, rowID + if columnOK { + view, id = ViewInverse, columnID + if !f.InverseEnabled() { + return nil, fmt.Errorf("Bitmap() cannot retrieve columns unless inverse storage enabled") + } + } + + frag := e.Index.Fragment(db, frame, view, slice) if frag == nil { return NewBitmap(), nil } - return frag.Bitmap(rowID), nil + return frag.Bitmap(id), nil } // executeIntersectSlice executes a intersect() call for a local slice. @@ -482,12 +511,6 @@ func (e *Executor) executeCount(ctx context.Context, db string, c *pql.Call, sli return n, nil } -// executeProfile executes a Profile() call. -// This call only executes locally since the profile attibutes are stored locally. -func (e *Executor) executeProfile(ctx context.Context, db string, c *pql.Call, opt *ExecOptions) (*Profile, error) { - panic("FIXME: impl: e.Index.ProfileAttr(c.ID)") -} - // executeClearBit executes a ClearBit() call. func (e *Executor) executeClearBit(ctx context.Context, db string, c *pql.Call, opt *ExecOptions) (bool, error) { view, _ := c.Args["view"].(string) @@ -1165,7 +1188,7 @@ func needsSlices(calls []*pql.Call) bool { } for _, call := range calls { switch call.Name { - case "ClearBit", "Profile", "SetBit", "SetBitmapAttrs", "SetProfileAttrs": + case "ClearBit", "SetBit", "SetBitmapAttrs", "SetProfileAttrs": continue case "Count", "TopN": return true diff --git a/executor_test.go b/executor_test.go index b6850c54b..d7c661765 100644 --- a/executor_test.go +++ b/executor_test.go @@ -2,6 +2,7 @@ package pilosa_test import ( "context" + "fmt" "reflect" "strings" "testing" @@ -13,23 +14,68 @@ import ( // Ensure a bitmap query can be executed. func TestExecutor_Execute_Bitmap(t *testing.T) { - idx := MustOpenIndex() - defer idx.Close() - idx.MustCreateFragmentIfNotExists("d", "f", pilosa.ViewStandard, 0).MustSetBits(10, 3) - idx.MustCreateFragmentIfNotExists("d", "f", pilosa.ViewStandard, 1).MustSetBits(10, SliceWidth+1) + t.Run("Row", func(t *testing.T) { + idx := MustOpenIndex() + defer idx.Close() + db := idx.MustCreateDBIfNotExists("d", pilosa.DBOptions{}) + f, err := db.CreateFrame("f", pilosa.FrameOptions{InverseEnabled: true}) + if err != nil { + t.Fatal(err) + } - if err := idx.Frame("d", "f").BitmapAttrStore().SetAttrs(10, map[string]interface{}{"foo": "bar", "baz": uint64(123)}); err != nil { - t.Fatal(err) - } + e := NewExecutor(idx.Index, NewCluster(1)) - e := NewExecutor(idx.Index, NewCluster(1)) - if res, err := e.Execute(context.Background(), "d", MustParse(`Bitmap(id=10, frame=f)`), nil, nil); err != nil { - t.Fatal(err) - } else if bits := res[0].(*pilosa.Bitmap).Bits(); !reflect.DeepEqual(bits, []uint64{3, SliceWidth + 1}) { - t.Fatalf("unexpected bits: %+v", bits) - } else if attrs := res[0].(*pilosa.Bitmap).Attrs; !reflect.DeepEqual(attrs, map[string]interface{}{"foo": "bar", "baz": uint64(123)}) { - t.Fatalf("unexpected attrs: %s", spew.Sdump(attrs)) - } + // Set bits. + if _, err := e.Execute(context.Background(), "d", MustParse(``+ + fmt.Sprintf("SetBit(frame=f, id=%d, profileID=%d)\n", 10, 3)+ + fmt.Sprintf("SetBit(frame=f, id=%d, profileID=%d)\n", 10, SliceWidth+1)+ + fmt.Sprintf("SetBit(frame=f, id=%d, profileID=%d)\n", 20, SliceWidth+1), + ), nil, nil); err != nil { + t.Fatal(err) + } + if err := f.BitmapAttrStore().SetAttrs(10, map[string]interface{}{"foo": "bar", "baz": uint64(123)}); err != nil { + t.Fatal(err) + } + + if res, err := e.Execute(context.Background(), "d", MustParse(`Bitmap(id=10, frame=f)`), nil, nil); err != nil { + t.Fatal(err) + } else if bits := res[0].(*pilosa.Bitmap).Bits(); !reflect.DeepEqual(bits, []uint64{3, SliceWidth + 1}) { + t.Fatalf("unexpected bits: %+v", bits) + } else if attrs := res[0].(*pilosa.Bitmap).Attrs; !reflect.DeepEqual(attrs, map[string]interface{}{"foo": "bar", "baz": uint64(123)}) { + t.Fatalf("unexpected attrs: %s", spew.Sdump(attrs)) + } + }) + + t.Run("Column", func(t *testing.T) { + idx := MustOpenIndex() + defer idx.Close() + db := idx.MustCreateDBIfNotExists("d", pilosa.DBOptions{}) + if _, err := db.CreateFrame("f", pilosa.FrameOptions{InverseEnabled: true}); err != nil { + t.Fatal(err) + } + + e := NewExecutor(idx.Index, NewCluster(1)) + + // Set bits. + if _, err := e.Execute(context.Background(), "d", MustParse(``+ + fmt.Sprintf("SetBit(frame=f, id=%d, profileID=%d)\n", 10, 3)+ + fmt.Sprintf("SetBit(frame=f, id=%d, profileID=%d)\n", 10, SliceWidth+1)+ + fmt.Sprintf("SetBit(frame=f, id=%d, profileID=%d)\n", 20, SliceWidth+1), + ), nil, nil); err != nil { + t.Fatal(err) + } + if err := db.ProfileAttrStore().SetAttrs(SliceWidth+1, map[string]interface{}{"foo": "bar", "baz": uint64(123)}); err != nil { + t.Fatal(err) + } + + if res, err := e.Execute(context.Background(), "d", MustParse(fmt.Sprintf(`Bitmap(profileID=%d, frame=f)`, SliceWidth+1)), nil, nil); err != nil { + t.Fatal(err) + } else if bits := res[0].(*pilosa.Bitmap).Bits(); !reflect.DeepEqual(bits, []uint64{10, 20}) { + t.Fatalf("unexpected bits: %+v", bits) + } else if attrs := res[0].(*pilosa.Bitmap).Attrs; !reflect.DeepEqual(attrs, map[string]interface{}{"foo": "bar", "baz": uint64(123)}) { + t.Fatalf("unexpected attrs: %s", spew.Sdump(attrs)) + } + }) } // Ensure a difference query can be executed.