From 748b034aa8e44546eac120ca5c858aab6b8aecc5 Mon Sep 17 00:00:00 2001 From: Michael Baird Date: Fri, 18 Aug 2017 09:36:45 -0500 Subject: [PATCH 1/3] DeleteView from Frame --- frame.go | 20 ++++++++++++++++++++ frame_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/frame.go b/frame.go index de3dd6fbb..295c3abf2 100644 --- a/frame.go +++ b/frame.go @@ -509,6 +509,26 @@ func (f *Frame) newView(path, name string) *View { return view } +// DeleteView removes a view from the frame. +func (f *Frame) DeleteView(name string) error { + view := f.views[name] + if view == nil { + return ErrInvalidView + } + + // TODO capture errors lower down in this method + _ = view.Close() + + // Delete view directory. + if err := os.RemoveAll(view.Path()); err != nil { + return err + } + + delete(f.views, name) + + return nil +} + // 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. diff --git a/frame_test.go b/frame_test.go index 8cb48cca8..b560d985e 100644 --- a/frame_test.go +++ b/frame_test.go @@ -307,3 +307,34 @@ func TestFrame_RowLabelValidation(t *testing.T) { } } + +// Ensure frame can open and retrieve a view. +func TestFrame_DeleteView(t *testing.T) { + f := test.MustOpenFrame() + defer f.Close() + + // Create view. + view, err := f.CreateViewIfNotExists("v") + if err != nil { + t.Fatal(err) + } else if view == nil { + t.Fatal("expected view") + } + + err = f.DeleteView("v") + if err != nil { + t.Fatal(err) + } + + if f.View("v") != nil { + t.Fatal("view still exists in frame") + } + + // // Retrieve existing view. + view2, err := f.CreateViewIfNotExists("v") + if err != nil { + t.Fatal(err) + } else if view == view2 { + t.Fatal("failed to create new view") + } +} From 753d89d45e63b746f0543da3e63c282b4b43d699 Mon Sep 17 00:00:00 2001 From: Michael Baird Date: Fri, 18 Aug 2017 14:55:29 -0500 Subject: [PATCH 2/3] handler for delete View --- handler.go | 29 +++++++++++++++++++++++++++++ handler_test.go | 20 ++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/handler.go b/handler.go index f3bf5c786..89f18a20b 100644 --- a/handler.go +++ b/handler.go @@ -116,6 +116,7 @@ func NewRouter(handler *Handler) *mux.Router { router.HandleFunc("/index/{index}/frame/{frame}/restore", handler.handlePostFrameRestore).Methods("POST") router.HandleFunc("/index/{index}/frame/{frame}/time-quantum", handler.handlePatchFrameTimeQuantum).Methods("PATCH") router.HandleFunc("/index/{index}/frame/{frame}/views", handler.handleGetFrameViews).Methods("GET") + router.HandleFunc("/index/{index}/frame/{frame}/view/{view}", handler.handleDeleteView).Methods("DELETE") router.HandleFunc("/index/{index}/input/{input-definition}", handler.handlePostInput).Methods("POST") router.HandleFunc("/index/{index}/input-definition/{input-definition}", handler.handleGetInputDefinition).Methods("GET") router.HandleFunc("/index/{index}/input-definition/{input-definition}", handler.handlePostInputDefinition).Methods("POST") @@ -789,6 +790,34 @@ func (h *Handler) handleGetFrameViews(w http.ResponseWriter, r *http.Request) { } } +// handleDeleteView handles Delete /frame/view request. +func (h *Handler) handleDeleteView(w http.ResponseWriter, r *http.Request) { + indexName := mux.Vars(r)["index"] + frameName := mux.Vars(r)["frame"] + viewName := mux.Vars(r)["view"] + + // Retrieve frame. + f := h.Holder.Frame(indexName, frameName) + if f == nil { + http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) + return + } + + if err := f.DeleteView(viewName); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // TODO: Send the delete frame message to all nodes. + + // Encode response. + if err := json.NewEncoder(w).Encode(deleteViewResponse{}); err != nil { + h.logger().Printf("response encoding error: %s", err) + } +} + +type deleteViewResponse struct{} + type getFrameViewsResponse struct { Views []string `json:"views,omitempty"` } diff --git a/handler_test.go b/handler_test.go index 22a1fd4f3..2947fd1b8 100644 --- a/handler_test.go +++ b/handler_test.go @@ -1511,3 +1511,23 @@ func TestHandler_GetTimeStamp(t *testing.T) { t.Fatalf("Expected Ignore nonexistent fields") } } + +// Ensure handler can delete a view. +func TestHandler_DeleteView(t *testing.T) { + hldr := test.MustOpenHolder() + defer hldr.Close() + hldr.MustCreateFragmentIfNotExists("i0", "f0", "v0", 1).MustSetBits(30, (1*SliceWidth)+1) + + h := test.NewHandler() + h.Holder = hldr.Holder + h.Cluster = test.NewCluster(1) + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i0/frame/f0/view/v0", strings.NewReader(""))) + if w.Code != http.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } else if f := hldr.Index("i0").Frame("f0").View("v0"); f != nil { + t.Fatal("expected nil view") + } +} From 80ab4cd1937b7ccbdaf09f6ff99bc1eadffeb246 Mon Sep 17 00:00:00 2001 From: Michael Baird Date: Fri, 18 Aug 2017 17:08:13 -0500 Subject: [PATCH 3/3] delete only time based views, and handle inverse views --- handler.go | 25 +++++++++++++++++++++++-- handler_test.go | 7 ++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/handler.go b/handler.go index 89f18a20b..d355213db 100644 --- a/handler.go +++ b/handler.go @@ -803,12 +803,33 @@ func (h *Handler) handleDeleteView(w http.ResponseWriter, r *http.Request) { return } - if err := f.DeleteView(viewName); err != nil { + // Check that the view name is an integer. + if _, err := strconv.Atoi(viewName); err != nil { + http.Error(w, "only time based views can be deleted", http.StatusBadRequest) + return + } + + // Check the frame for time quantum. + if f.TimeQuantum() == "" { + http.Error(w, "frame does not contain a Time Quantum", http.StatusBadRequest) + return + } + + // Delete the standard view. + if err := f.DeleteView(ViewStandard + "_" + viewName); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - // TODO: Send the delete frame message to all nodes. + // Delete the inverse view. + if f.InverseEnabled() { + if err := f.DeleteView(ViewInverse + "_" + viewName); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + } + + // TODO: Send the delete view message to all nodes. // Encode response. if err := json.NewEncoder(w).Encode(deleteViewResponse{}); err != nil { diff --git a/handler_test.go b/handler_test.go index 2947fd1b8..c8e0d949c 100644 --- a/handler_test.go +++ b/handler_test.go @@ -1516,18 +1516,19 @@ func TestHandler_GetTimeStamp(t *testing.T) { func TestHandler_DeleteView(t *testing.T) { hldr := test.MustOpenHolder() defer hldr.Close() - hldr.MustCreateFragmentIfNotExists("i0", "f0", "v0", 1).MustSetBits(30, (1*SliceWidth)+1) + hldr.MustCreateFragmentIfNotExists("i0", "f0", pilosa.ViewStandard+"_2017", 1).MustSetBits(30, (1*SliceWidth)+1) + hldr.Index("i0").Frame("f0").SetTimeQuantum("YMD") h := test.NewHandler() h.Holder = hldr.Holder h.Cluster = test.NewCluster(1) w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i0/frame/f0/view/v0", strings.NewReader(""))) + h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i0/frame/f0/view/2017", strings.NewReader(""))) if w.Code != http.StatusOK { t.Fatalf("unexpected status code: %d", w.Code) } else if body := w.Body.String(); body != `{}`+"\n" { t.Fatalf("unexpected body: %s", body) - } else if f := hldr.Index("i0").Frame("f0").View("v0"); f != nil { + } else if f := hldr.Index("i0").Frame("f0").View(pilosa.ViewStandard + "_2017"); f != nil { t.Fatal("expected nil view") } }