diff --git a/frame.go b/frame.go index 081f71700..e7bfd4a40 100644 --- a/frame.go +++ b/frame.go @@ -435,10 +435,28 @@ func (f *Frame) CreateField(field *Field) error { return err } f.schema = schema - + f.saveSchema() return nil } +// GetFields returns a list of all the fields in the frame. +func (f *Frame) GetFields() (*FrameSchema, error) { + f.mu.RLock() + defer f.mu.RUnlock() + + // Ensure the frame supports fields. + if !f.RangeEnabled() { + return nil, ErrFrameFieldsNotAllowed + } + + err := f.loadSchema() + if err != nil { + return nil, err + } + + return f.schema, nil +} + // DeleteField deletes an existing field on the schema. func (f *Frame) DeleteField(name string) error { f.mu.Lock() diff --git a/handler.go b/handler.go index 8151116ee..d8463862a 100644 --- a/handler.go +++ b/handler.go @@ -119,6 +119,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}/field/{field}", handler.handlePostFrameField).Methods("POST") + router.HandleFunc("/index/{index}/frame/{frame}/fields", handler.handleGetFrameFields).Methods("GET") router.HandleFunc("/index/{index}/frame/{frame}/field/{field}", handler.handleDeleteFrameField).Methods("DELETE") router.HandleFunc("/index/{index}/frame/{frame}/views", handler.handleGetFrameViews).Methods("GET") router.HandleFunc("/index/{index}/frame/{frame}/view/{view}", handler.handleDeleteView).Methods("DELETE") @@ -842,6 +843,41 @@ func (h *Handler) handleDeleteFrameField(w http.ResponseWriter, r *http.Request) } } +func (h *Handler) handleGetFrameFields(w http.ResponseWriter, r *http.Request) { + indexName := mux.Vars(r)["index"] + frameName := mux.Vars(r)["frame"] + + index := h.Holder.index(indexName) + if index == nil { + http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) + return + } + + frame := index.frame(frameName) + if frame == nil { + http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) + return + } + + schema, err := frame.GetFields() + if err == ErrFrameFieldsNotAllowed { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } else if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Encode response. + if err := json.NewEncoder(w).Encode(getFrameFieldsResponse{Fields: schema.Fields}); err != nil { + h.logger().Printf("response encoding error: %s", err) + } +} + +type getFrameFieldsResponse struct { + Fields []*Field `json:"fields,omitempty"` +} + type deleteFrameFieldRequest struct{} type deleteFrameFieldResponse struct{} diff --git a/handler_test.go b/handler_test.go index 8159cf586..6e8ebab88 100644 --- a/handler_test.go +++ b/handler_test.go @@ -1030,6 +1030,76 @@ func TestHandler_Frame_DeleteField(t *testing.T) { }) } +func TestHandler_Frame_GetFields(t *testing.T) { + hldr := test.MustOpenHolder() + defer hldr.Close() + + s := test.NewServer() + s.Handler.Holder = hldr.Holder + defer s.Close() + + t.Run("OK", func(t *testing.T) { + idx := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) + f, err := idx.CreateFrameIfNotExists("f", pilosa.FrameOptions{RangeEnabled: true}) + if err != nil { + t.Fatal(err) + } else if err := f.CreateField(&pilosa.Field{Name: "x", Type: pilosa.FieldTypeInt, Min: 1, Max: 100}); err != nil { + t.Fatal(err) + } + resp, err := http.Get(s.URL + "/index/i/frame/f/fields") + if err != nil { + t.Fatal(err) + } + if err != nil { + t.Fatal(err) + } else if resp.StatusCode != http.StatusOK { + t.Fatalf("unexpected status code: %d", resp.StatusCode) + } + + var fields FrameFields + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + if err = json.Unmarshal([]byte(body), &fields); err != nil { + t.Fatal(err) + } + field := fields.Fields[0] + if field.Name != "x" { + t.Fatalf("expected field's name: x, actuall name: %v", field.Name) + } else if field.Min != 1 { + t.Fatalf("expected field's min: x, actuall min: %v", field.Min) + } else if field.Max != 100 { + t.Fatalf("expected field's max: x, actuall max: %v", field.Max) + } + + }) + + t.Run("ErrFrameFieldNotAllowed", func(t *testing.T) { + idx := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) + _, err := idx.CreateFrameIfNotExists("f1", pilosa.FrameOptions{RangeEnabled: false}) + + resp, err := http.Get(s.URL + "/index/i/frame/f1/fields") + if err != nil { + t.Fatal(err) + } + if err != nil { + t.Fatal(err) + } else if resp.StatusCode != http.StatusBadRequest { + t.Fatalf("unexpected status code: %d", resp.StatusCode) + } else if body, err := ioutil.ReadAll(resp.Body); err != nil { + t.Fatal(err) + } else if strings.TrimSpace(string(body)) != `frame fields not allowed` { + t.Fatalf("unexpected body: %q", body) + } + }) + +} + +type FrameFields struct { + Fields []pilosa.Field +} + // Ensure the handler can backup a fragment and then restore it. func TestHandler_Fragment_BackupRestore(t *testing.T) { hldr := test.MustOpenHolder() diff --git a/index.go b/index.go index 67aae389d..6e38b19aa 100644 --- a/index.go +++ b/index.go @@ -492,6 +492,8 @@ func (i *Index) createFrame(name string, opt FrameOptions) (*Frame, error) { return nil, err } + f.rangeEnabled = opt.RangeEnabled + // Set schema & save. f.schema = &FrameSchema{ Fields: opt.Fields,