mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
validate columnID match primaryField
This commit is contained in:
parent
7a3baed3ac
commit
53c7e5946d
5 changed files with 106 additions and 23 deletions
24
handler.go
24
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"]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -540,7 +540,7 @@ func TestMain_SendReceiveMessage(t *testing.T) {
|
|||
"cacheType": "ranked",
|
||||
"timeQuantum": "YMD"
|
||||
}}],
|
||||
"fields": [{"name": "id",
|
||||
"fields": [{"name": "columnID",
|
||||
"primaryKey": true
|
||||
}]}
|
||||
`); err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue