diff --git a/handler.go b/handler.go index 9a9617994..e2200bf21 100644 --- a/handler.go +++ b/handler.go @@ -1499,6 +1499,7 @@ func errorString(err error) string { return err.Error() } +// handlePOSTInputDefinition handles POST /input-definition request. func (h *Handler) handlePostInputDefinition(w http.ResponseWriter, r *http.Request) { indexName := mux.Vars(r)["index"] inputDefName := mux.Vars(r)["input-definition"] @@ -1525,6 +1526,26 @@ func (h *Handler) handlePostInputDefinition(w http.ResponseWriter, r *http.Reque } def.Name = inputDefName + // Validate columnLabel & duplicate primaryKey + numPrimaryKey := 0 + for _, field := range def.Fields { + if field.PrimaryKey { + numPrimaryKey += 1 + if field.Name == index.columnLabel { + continue + } else { + err = fmt.Errorf("primary field's name not match columnLabel") + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + } + } + if numPrimaryKey > 1 { + err = errors.New("duplicate primaryKey with other field") + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + // Create InputDefinition. _, err = index.CreateInputDefinition(def) if err == ErrInputDefinitionExists { @@ -1550,6 +1571,7 @@ func (h *Handler) handlePostInputDefinition(w http.ResponseWriter, r *http.Reque } } +// handleGetInputDefinition handles GET /input-definition request. func (h *Handler) handleGetInputDefinition(w http.ResponseWriter, r *http.Request) { indexName := mux.Vars(r)["index"] inputDefName := mux.Vars(r)["input-definition"] @@ -1563,7 +1585,6 @@ func (h *Handler) handleGetInputDefinition(w http.ResponseWriter, r *http.Reques return } inputDef, _ := index.inputDefinitions[inputDefName] - //inputInfo := InputDefinitionInfo{Frames: inputDef.frames, Fields: inputDef.fields} if err := json.NewEncoder(w).Encode(InputDefinitionInfo{ Frames: inputDef.frames, Fields: inputDef.fields, @@ -1573,6 +1594,7 @@ func (h *Handler) handleGetInputDefinition(w http.ResponseWriter, r *http.Reques } +// handleDeleteInputDefinition handles DELETE /input-definition request. func (h *Handler) handleDeleteInputDefinition(w http.ResponseWriter, r *http.Request) { indexName := mux.Vars(r)["index"] inputDefName := mux.Vars(r)["input-definition"] diff --git a/handler_test.go b/handler_test.go index 35d57e912..34e3e4585 100644 --- a/handler_test.go +++ b/handler_test.go @@ -1084,7 +1084,7 @@ func TestHandler_CreateInputDefinition(t *testing.T) { }], "fields": [ { - "name": "id", + "name": "columnID", "primaryKey": true }, { @@ -1112,6 +1112,80 @@ func TestHandler_CreateInputDefinition(t *testing.T) { } else if body := w.Body.String(); body != `{}`+"\n" { t.Fatalf("unexpected body: %s", body) } + +} + +//Ensure throwing error if there's duplicated primaryKey field +func TestHandler_DuplicatePrimaryKey(t *testing.T) { + hldr := MustOpenHolder() + defer hldr.Close() + hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) + inputBody1 := []byte(` + { + "frames":[{ + "name":"event-time", + "options":{ + "timeQuantum": "YMD", + "inverseEnabled": false, + "cacheType": "ranked" + } + }], + "fields": [ + { + "name": "columnID", + "primaryKey": true + }, + { + "name": "columnID", + "primaryKey": true + } + ] + }`) + h := NewHandler() + h.Holder = hldr.Holder + h.Cluster = NewCluster(1) + w := httptest.NewRecorder() + h.ServeHTTP(w, MustNewHTTPRequest("POST", "/index/i0/input-definition/input2", bytes.NewBuffer(inputBody1))) + if w.Code != http.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `duplicate primaryKey with other field`+"\n" { + t.Fatalf("unexpected body: %s", body) + } +} + +// Eusure throwing error if primary field's name doesn't match columnLabel +func TestHandler_UnmatchColumnID(t *testing.T) { + hldr := MustOpenHolder() + defer hldr.Close() + hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{ColumnLabel: "id"}) + inputBody := []byte(` + { + "frames":[{ + "name":"event-time", + "options":{ + "timeQuantum": "YMD", + "inverseEnabled": false, + "cacheType": "ranked" + } + }], + "fields": [ + { + "name": "columnID", + "primaryKey": true + } + ] + }`) + h := NewHandler() + h.Holder = hldr.Holder + h.Cluster = NewCluster(1) + w := httptest.NewRecorder() + h.ServeHTTP(w, MustNewHTTPRequest("POST", "/index/i0/input-definition/input1", bytes.NewBuffer(inputBody))) + if w.Code != http.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `primary field's name not match columnLabel`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + } // Ensure handler can delete a input definition. diff --git a/input_definition.go b/input_definition.go index 1e8fd4680..6eb72e43e 100644 --- a/input_definition.go +++ b/input_definition.go @@ -99,7 +99,6 @@ func (i *InputDefinition) LoadDefinition(pb *internal.InputDefinition) error { i.frames = append(i.frames, inputFrame) } - numPrimaryKey := 0 countRowID := make(map[string]uint64) for _, field := range pb.Fields { var actions []Action @@ -122,13 +121,6 @@ func (i *InputDefinition) LoadDefinition(pb *internal.InputDefinition) error { RowID: &action.RowID, }) } - if field.PrimaryKey { - numPrimaryKey += 1 - } - - if numPrimaryKey > 1 { - return errors.New("duplicate primaryKey with other field") - } inputField := InputDefinitionField{ Name: field.Name, @@ -252,6 +244,7 @@ func (o *Action) Encode() (*internal.InputDefinitionAction, error) { }, nil } +// convert pointer to uint64 func convert(x *uint64) uint64 { if x != nil { return *x @@ -267,8 +260,8 @@ type InputFrame struct { // InputDefinitionInfo the json message format to create an InputDefinition. type InputDefinitionInfo struct { - Frames []InputFrame `json:"frames"` - Fields []InputDefinitionField `json:"fields"` + Frames []InputFrame `json:"frames"` + Fields []InputDefinitionField `json:"fields"` } // Encode converts InputDefinitionInfo into its internal representation. @@ -288,6 +281,7 @@ func (i *InputDefinitionInfo) Encode() (*internal.InputDefinition, error) { return &def, nil } +// AddFrame adds frame to input definition func (i *InputDefinition) AddFrame(frame InputFrame) error { i.frames = append(i.frames, frame) if err := i.saveMeta(); err != nil { @@ -296,6 +290,7 @@ func (i *InputDefinition) AddFrame(frame InputFrame) error { return nil } +// ValidateAction validate actions from input-definition func (i *InputDefinition) ValidateAction(action *internal.InputDefinitionAction) error { if action.Frame == "" { return ErrFrameRequired diff --git a/input_definition_test.go b/input_definition_test.go index aa6acd5f8..76b55c7f0 100644 --- a/input_definition_test.go +++ b/input_definition_test.go @@ -128,16 +128,8 @@ func TestInputDefinition_LoadDefinition(t *testing.T) { } action = internal.InputDefinitionAction{Frame: "f", ValueDestination: pilosa.SingleRowBool, RowID: 100} - action1 := internal.InputDefinitionAction{Frame: "f", ValueDestination: pilosa.SingleRowBool, RowID: 0} - field1 := internal.InputDefinitionField{Name: "newID", PrimaryKey: true, InputDefinitionActions: []*internal.InputDefinitionAction{&action1}} - def = &internal.InputDefinition{Name: "test", Frames: []*internal.Frame{&frames}, Fields: []*internal.InputDefinitionField{&field, &field1}} - err = input.LoadDefinition(def) - if !strings.Contains(err.Error(), "duplicate primaryKey with other field") { - t.Fatalf("Expected duplicate primaryKey error, actual error: %s", err) - } - - action1 = internal.InputDefinitionAction{Frame: "f", ValueDestination: pilosa.SingleRowBool, RowID: 100} - field1 = internal.InputDefinitionField{Name: "id", PrimaryKey: true, InputDefinitionActions: []*internal.InputDefinitionAction{&action1}} + action1 := internal.InputDefinitionAction{Frame: "f", ValueDestination: pilosa.SingleRowBool, RowID: 100} + field1 := internal.InputDefinitionField{Name: "id", PrimaryKey: true, InputDefinitionActions: []*internal.InputDefinitionAction{&action1}} def = &internal.InputDefinition{Name: "test", Frames: []*internal.Frame{&frames}, Fields: []*internal.InputDefinitionField{&field, &field1}} err = input.LoadDefinition(def) if !strings.Contains(err.Error(), "duplicate rowID with other field") { diff --git a/server/server_test.go b/server/server_test.go index 6f4570709..b6af8c423 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -540,7 +540,7 @@ func TestMain_SendReceiveMessage(t *testing.T) { "cacheType": "ranked", "timeQuantum": "YMD" }}], - "fields": [{"name": "id", + "fields": [{"name": "columnID", "primaryKey": true }]} `); err != nil {