From 2731311378a3b35823ae6d507260eb4b81f3f73b Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Tue, 4 Apr 2017 14:45:36 -0600 Subject: [PATCH] Refactor higher level view interface. --- executor.go | 76 +++++++++++++++++++++++++++++++++++++++++------- executor_test.go | 18 ++++++------ frame.go | 48 ++++++------------------------ frame_test.go | 4 +-- handler_test.go | 20 +++++++------ pilosa.go | 2 ++ view.go | 5 ++++ 7 files changed, 103 insertions(+), 70 deletions(-) diff --git a/executor.go b/executor.go index db67b9d0b..efb9e8199 100644 --- a/executor.go +++ b/executor.go @@ -490,26 +490,27 @@ func (e *Executor) executeProfile(ctx context.Context, db string, c *pql.Call, o // 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) frame, ok := c.Args["frame"].(string) if !ok { return false, errors.New("ClearBit() frame required") } - // Lookup column label. + // Retrieve frame. d := e.Index.DB(db) if d == nil { - return false, nil + return false, ErrDatabaseNotFound } - columnLabel := d.ColumnLabel() - - // Lookup row label. - f := e.Index.Frame(db, frame) + f := d.Frame(frame) if f == nil { - return false, nil + return false, ErrFrameNotFound } + + // Retrieve labels. + columnLabel := d.ColumnLabel() rowLabel := f.RowLabel() - // Read row & column ids. + // Read fields using labels. rowID, ok := c.Args[rowLabel].(uint64) if !ok { return false, fmt.Errorf("ClearBit() field required: %s", rowLabel) @@ -520,12 +521,38 @@ func (e *Executor) executeClearBit(ctx context.Context, db string, c *pql.Call, return false, fmt.Errorf("ClearBit() field required: %s", columnLabel) } + // Clear bits for each view. + switch view { + case ViewStandard: + return e.executeClearBitView(ctx, db, c, f, view, colID, rowID, opt) + case ViewInverse: + return e.executeClearBitView(ctx, db, c, f, view, rowID, colID, opt) + case "": + var ret bool + if changed, err := e.executeClearBitView(ctx, db, c, f, ViewStandard, colID, rowID, opt); err != nil { + return ret, err + } else if changed { + ret = true + } + if changed, err := e.executeClearBitView(ctx, db, c, f, ViewInverse, rowID, colID, opt); err != nil { + return ret, err + } else if changed { + ret = true + } + return ret, nil + default: + return false, fmt.Errorf("invalid view: %s", view) + } +} + +// executeClearBitView executes a ClearBit() call for a single view. +func (e *Executor) executeClearBitView(ctx context.Context, db string, c *pql.Call, f *Frame, view string, colID, rowID uint64, opt *ExecOptions) (bool, error) { slice := colID / SliceWidth ret := false for _, node := range e.Cluster.FragmentNodes(db, slice) { // Update locally if host matches. if node.Host == e.Host { - val, err := f.ClearBit(rowID, colID, nil) + val, err := f.ClearBit(view, rowID, colID, nil) if err != nil { return false, err } else if val { @@ -550,6 +577,7 @@ func (e *Executor) executeClearBit(ctx context.Context, db string, c *pql.Call, // executeSetBit executes a SetBit() call. func (e *Executor) executeSetBit(ctx context.Context, db string, c *pql.Call, opt *ExecOptions) (bool, error) { + view, _ := c.Args["view"].(string) frame, ok := c.Args["frame"].(string) if !ok { return false, errors.New("SetBit() field required: frame") @@ -558,7 +586,7 @@ func (e *Executor) executeSetBit(ctx context.Context, db string, c *pql.Call, op // Retrieve frame. d := e.Index.DB(db) if d == nil { - return false, ErrFrameNotFound + return false, ErrDatabaseNotFound } f := d.Frame(frame) if f == nil { @@ -590,13 +618,39 @@ func (e *Executor) executeSetBit(ctx context.Context, db string, c *pql.Call, op timestamp = &t } + // Set bits for each view. + switch view { + case ViewStandard: + return e.executeSetBitView(ctx, db, c, f, view, colID, rowID, timestamp, opt) + case ViewInverse: + return e.executeSetBitView(ctx, db, c, f, view, rowID, colID, timestamp, opt) + case "": + var ret bool + if changed, err := e.executeSetBitView(ctx, db, c, f, ViewStandard, colID, rowID, timestamp, opt); err != nil { + return ret, err + } else if changed { + ret = true + } + if changed, err := e.executeSetBitView(ctx, db, c, f, ViewInverse, rowID, colID, timestamp, opt); err != nil { + return ret, err + } else if changed { + ret = true + } + return ret, nil + default: + return false, fmt.Errorf("invalid view: %s", view) + } +} + +// executeSetBitView executes a SetBit() call for a specific view. +func (e *Executor) executeSetBitView(ctx context.Context, db string, c *pql.Call, f *Frame, view string, colID, rowID uint64, timestamp *time.Time, opt *ExecOptions) (bool, error) { slice := colID / SliceWidth ret := false for _, node := range e.Cluster.FragmentNodes(db, slice) { // Update locally if host matches. if node.Host == e.Host { - val, err := f.SetBit(rowID, colID, timestamp) + val, err := f.SetBit(view, rowID, colID, timestamp) if err != nil { return false, err } else if val { diff --git a/executor_test.go b/executor_test.go index 424aba12a..b6850c54b 100644 --- a/executor_test.go +++ b/executor_test.go @@ -390,16 +390,16 @@ func TestExecutor_Execute_Range(t *testing.T) { } // Set bits. - f.MustSetBit(1, 2, MustParseTimePtr("1999-12-31 00:00")) - f.MustSetBit(1, 3, MustParseTimePtr("2000-01-01 00:00")) - f.MustSetBit(1, 4, MustParseTimePtr("2000-01-02 00:00")) - f.MustSetBit(1, 5, MustParseTimePtr("2000-02-01 00:00")) - f.MustSetBit(1, 6, MustParseTimePtr("2001-01-01 00:00")) - f.MustSetBit(1, 7, MustParseTimePtr("2002-01-01 02:00")) + f.MustSetBit(pilosa.ViewStandard, 1, 2, MustParseTimePtr("1999-12-31 00:00")) + f.MustSetBit(pilosa.ViewStandard, 1, 3, MustParseTimePtr("2000-01-01 00:00")) + f.MustSetBit(pilosa.ViewStandard, 1, 4, MustParseTimePtr("2000-01-02 00:00")) + f.MustSetBit(pilosa.ViewStandard, 1, 5, MustParseTimePtr("2000-02-01 00:00")) + f.MustSetBit(pilosa.ViewStandard, 1, 6, MustParseTimePtr("2001-01-01 00:00")) + f.MustSetBit(pilosa.ViewStandard, 1, 7, MustParseTimePtr("2002-01-01 02:00")) - f.MustSetBit(1, 2, MustParseTimePtr("1999-12-30 00:00")) // too early - f.MustSetBit(1, 2, MustParseTimePtr("2002-02-01 00:00")) // too late - f.MustSetBit(10, 2, MustParseTimePtr("2001-01-01 00:00")) // different bitmap + f.MustSetBit(pilosa.ViewStandard, 1, 2, MustParseTimePtr("1999-12-30 00:00")) // too early + f.MustSetBit(pilosa.ViewStandard, 1, 2, MustParseTimePtr("2002-02-01 00:00")) // too late + f.MustSetBit(pilosa.ViewStandard, 10, 2, MustParseTimePtr("2001-01-01 00:00")) // different bitmap e := NewExecutor(idx.Index, NewCluster(1)) if res, err := e.Execute(context.Background(), "d", MustParse(`Range(id=1, frame=f, start="1999-12-31T00:00", end="2002-01-01T03:00")`), nil, nil); err != nil { diff --git a/frame.go b/frame.go index 9d0fccf50..06e5b0527 100644 --- a/frame.go +++ b/frame.go @@ -348,28 +348,13 @@ func (f *Frame) newView(path, name string) *View { return view } -// SetBit sets a bit within the frame. -func (f *Frame) SetBit(rowID, colID uint64, t *time.Time) (changed bool, err error) { - // Set standard layout bits. - if v, err := f.setBit(ViewStandard, rowID, colID, t); err != nil { - return changed, err - } else if v { - changed = v +// SetBit sets a bit on a view within the frame. +func (f *Frame) SetBit(name string, rowID, colID uint64, t *time.Time) (changed bool, err error) { + // Validate view name. + if !IsValidView(name) { + return false, ErrInvalidView } - // Set inverse layout bits. - // NOTE: The row & col are transposed for the inverted view. - if v, err := f.setBit(ViewInverse, colID, rowID, t); err != nil { - return changed, err - } else if v { - changed = v - } - - return changed, nil -} - -// setBit sets a bit for a given layout (default or inverted). -func (f *Frame) setBit(name string, rowID, colID uint64, t *time.Time) (changed bool, err error) { // Retrieve view. Exit if it doesn't exist. view, err := f.CreateViewIfNotExists(name) if err != nil { @@ -406,27 +391,12 @@ func (f *Frame) setBit(name string, rowID, colID uint64, t *time.Time) (changed } // ClearBit clears a bit within the frame. -func (f *Frame) ClearBit(rowID, colID uint64, t *time.Time) (changed bool, err error) { - // Clear standard layout bits. - if v, err := f.clearBit(ViewStandard, rowID, colID, t); err != nil { - return changed, err - } else if v { - changed = v +func (f *Frame) ClearBit(name string, rowID, colID uint64, t *time.Time) (changed bool, err error) { + // Validate view name. + if !IsValidView(name) { + return false, ErrInvalidView } - // Clear inverse layout bits. - // NOTE: The row & col are transposed for the inverted view. - if v, err := f.clearBit(ViewInverse, colID, rowID, t); err != nil { - return changed, err - } else if v { - changed = v - } - - return changed, nil -} - -// clearBit clears a bit for a given layout (default or inverted). -func (f *Frame) clearBit(name string, rowID, colID uint64, t *time.Time) (changed bool, err error) { // Retrieve view. Exit if it doesn't exist. view, err := f.CreateViewIfNotExists(name) if err != nil { diff --git a/frame_test.go b/frame_test.go index 8a86f1ee8..5b659f826 100644 --- a/frame_test.go +++ b/frame_test.go @@ -119,8 +119,8 @@ func (f *Frame) Reopen() error { } // MustSetBit sets a bit on the frame. Panic on error. -func (f *Frame) MustSetBit(bitmapID, profileID uint64, t *time.Time) (changed bool) { - changed, err := f.SetBit(bitmapID, profileID, t) +func (f *Frame) MustSetBit(view string, bitmapID, profileID uint64, t *time.Time) (changed bool) { + changed, err := f.SetBit(view, bitmapID, profileID, t) if err != nil { panic(err) } diff --git a/handler_test.go b/handler_test.go index 20f76de05..257e59713 100644 --- a/handler_test.go +++ b/handler_test.go @@ -39,12 +39,14 @@ func TestHandler_Schema(t *testing.T) { if f, err := d0.CreateFrameIfNotExists("f1", pilosa.FrameOptions{}); err != nil { t.Fatal(err) - } else if _, err := f.SetBit(0, 0, nil); err != nil { + } else if _, err := f.SetBit(pilosa.ViewStandard, 0, 0, nil); err != nil { + t.Fatal(err) + } else if _, err := f.SetBit(pilosa.ViewInverse, 0, 0, nil); err != nil { t.Fatal(err) } if f, err := d1.CreateFrameIfNotExists("f0", pilosa.FrameOptions{}); err != nil { t.Fatal(err) - } else if _, err := f.SetBit(0, 0, nil); err != nil { + } else if _, err := f.SetBit(pilosa.ViewStandard, 0, 0, nil); err != nil { t.Fatal(err) } if _, err := d0.CreateFrameIfNotExists("f0", pilosa.FrameOptions{}); err != nil { @@ -57,7 +59,7 @@ func TestHandler_Schema(t *testing.T) { h.ServeHTTP(w, MustNewHTTPRequest("GET", "/schema", nil)) if w.Code != http.StatusOK { t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"dbs":[{"name":"d0","frames":[{"name":"f0"},{"name":"f1","views":[{"name":"inverse"},{"name":"standard"}]}]},{"name":"d1","frames":[{"name":"f0","views":[{"name":"inverse"},{"name":"standard"}]}]}]}`+"\n" { + } else if body := w.Body.String(); body != `{"dbs":[{"name":"d0","frames":[{"name":"f0"},{"name":"f1","views":[{"name":"inverse"},{"name":"standard"}]}]},{"name":"d1","frames":[{"name":"f0","views":[{"name":"standard"}]}]}]}`+"\n" { t.Fatalf("unexpected body: %s", body) } } @@ -95,11 +97,11 @@ func TestHandler_MaxSlices_Inverse(t *testing.T) { if err != nil { t.Fatal(err) } - if _, err := f0.SetBit((1*SliceWidth)+1, 30, nil); err != nil { + if _, err := f0.SetBit(pilosa.ViewInverse, 30, (1*SliceWidth)+1, nil); err != nil { t.Fatal(err) - } else if _, err := f0.SetBit((1*SliceWidth)+2, 30, nil); err != nil { + } else if _, err := f0.SetBit(pilosa.ViewInverse, 30, (1*SliceWidth)+2, nil); err != nil { t.Fatal(err) - } else if _, err := f0.SetBit((3*SliceWidth)+4, 30, nil); err != nil { + } else if _, err := f0.SetBit(pilosa.ViewInverse, 30, (3*SliceWidth)+4, nil); err != nil { t.Fatal(err) } @@ -107,11 +109,11 @@ func TestHandler_MaxSlices_Inverse(t *testing.T) { if err != nil { t.Fatal(err) } - if _, err := f1.SetBit((0*SliceWidth)+1, 40, nil); err != nil { + if _, err := f1.SetBit(pilosa.ViewStandard, 40, (0*SliceWidth)+1, nil); err != nil { t.Fatal(err) - } else if _, err := f1.SetBit((0*SliceWidth)+2, 40, nil); err != nil { + } else if _, err := f1.SetBit(pilosa.ViewInverse, 40, (0*SliceWidth)+2, nil); err != nil { t.Fatal(err) - } else if _, err := f1.SetBit((0*SliceWidth)+4, 40, nil); err != nil { + } else if _, err := f1.SetBit(pilosa.ViewInverse, 40, (0*SliceWidth)+4, nil); err != nil { t.Fatal(err) } diff --git a/pilosa.go b/pilosa.go index d7e61e46b..93dd9fef3 100644 --- a/pilosa.go +++ b/pilosa.go @@ -20,6 +20,8 @@ var ( ErrFrameExists = errors.New("frame already exists") ErrFrameNotFound = errors.New("frame not found") + ErrInvalidView = errors.New("invalid veiw") + ErrName = errors.New("invalid database or frame's name, must match [a-z0-9_-]") // ErrFragmentNotFound is returned when a fragment does not exist. diff --git a/view.go b/view.go index b53cea6aa..a04764aa8 100644 --- a/view.go +++ b/view.go @@ -17,6 +17,11 @@ const ( ViewInverse = "inverse" ) +// IsValidView returns true if name is valid. +func IsValidView(name string) bool { + return name == ViewStandard || name == ViewInverse +} + // View represents a container for frame data. type View struct { mu sync.Mutex