From adc629604001031080847de454b1a0f73af4fdb3 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Mon, 10 Jul 2017 17:33:43 -0600 Subject: [PATCH] Add PQL support for field values. This commit adds support in the PQL language to set field values. --- executor.go | 73 ++++++++++++++++++++++++++++++++++++ executor_test.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ pilosa.go | 1 + 3 files changed, 171 insertions(+) diff --git a/executor.go b/executor.go index 6cb0e9e7a..d58be42f1 100644 --- a/executor.go +++ b/executor.go @@ -168,6 +168,8 @@ func (e *Executor) executeCall(ctx context.Context, index string, c *pql.Call, s return e.executeCount(ctx, index, c, slices, opt) case "SetBit": return e.executeSetBit(ctx, index, c, opt) + case "SetFieldValue": + return nil, e.executeSetFieldValue(ctx, index, c, opt) case "SetRowAttrs": return nil, e.executeSetRowAttrs(ctx, index, c, opt) case "SetColumnAttrs": @@ -839,6 +841,77 @@ func (e *Executor) executeSetBitView(ctx context.Context, index string, c *pql.C return ret, nil } +// executeSetFieldValue executes a SetFieldValue() call. +func (e *Executor) executeSetFieldValue(ctx context.Context, index string, c *pql.Call, opt *ExecOptions) error { + frameName, ok := c.Args["frame"].(string) + if !ok { + return errors.New("SetFieldValue() frame required") + } + + // Retrieve column label. + idx := e.Holder.Index(index) + if idx == nil { + return ErrIndexNotFound + } + columnLabel := idx.ColumnLabel() + + // Retrieve frame. + frame := e.Holder.Frame(index, frameName) + if frame == nil { + return ErrFrameNotFound + } + + // Parse labels. + columnID, ok, err := c.UintArg(columnLabel) + if err != nil { + return fmt.Errorf("reading SetFieldValue() column: %v", err) + } else if !ok { + return fmt.Errorf("SetFieldValue() column field '%v' required", columnLabel) + } + + // Copy args and remove reserved fields. + args := pql.CopyArgs(c.Args) + delete(args, "frame") + delete(args, columnLabel) + + // Set values. + for name, value := range args { + switch value := value.(type) { + case int64: + if _, err := frame.SetFieldValue(columnID, name, value); err != nil { + return err + } + default: + return ErrInvalidFieldValueType + } + } + frame.Stats.Count("SetFieldValue", 1, 1.0) + + // Do not forward call if this is already being forwarded. + if opt.Remote { + return nil + } + + // Execute on remote nodes in parallel. + nodes := Nodes(e.Cluster.Nodes).FilterHost(e.Host) + resp := make(chan error, len(nodes)) + for _, node := range nodes { + go func(node *Node) { + _, err := e.exec(ctx, node, index, &pql.Query{Calls: []*pql.Call{c}}, nil, opt) + resp <- err + }(node) + } + + // Return first error. + for range nodes { + if err := <-resp; err != nil { + return err + } + } + + return nil +} + // executeSetRowAttrs executes a SetRowAttrs() call. func (e *Executor) executeSetRowAttrs(ctx context.Context, index string, c *pql.Call, opt *ExecOptions) error { frameName, ok := c.Args["frame"].(string) diff --git a/executor_test.go b/executor_test.go index d4714b714..3c0f7e8ad 100644 --- a/executor_test.go +++ b/executor_test.go @@ -234,6 +234,103 @@ func TestExecutor_Execute_SetBit(t *testing.T) { } } +// Ensure a SetFieldValue() query can be executed. +func TestExecutor_Execute_SetFieldValue(t *testing.T) { + t.Run("OK", func(t *testing.T) { + hldr := test.MustOpenHolder() + defer hldr.Close() + + // Create frames. + index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) + if _, err := index.CreateFrameIfNotExists("f", pilosa.FrameOptions{ + RangeEnabled: true, + Fields: []*pilosa.Field{ + {Name: "field0", Type: pilosa.FieldTypeInt, Min: 0, Max: 50}, + {Name: "field1", Type: pilosa.FieldTypeInt, Min: 1, Max: 2}, + }, + }); err != nil { + t.Fatal(err) + } else if _, err := index.CreateFrameIfNotExists("xxx", pilosa.FrameOptions{}); err != nil { + t.Fatal(err) + } + + // Set field values. + e := test.NewExecutor(hldr.Holder, test.NewCluster(1)) + if _, err := e.Execute(context.Background(), "i", test.MustParse(`SetFieldValue(columnID=10, frame=f, field0=25, field1=2)`), nil, nil); err != nil { + t.Fatal(err) + } else if _, err := e.Execute(context.Background(), "i", test.MustParse(`SetFieldValue(columnID=100, frame=f, field0=10)`), nil, nil); err != nil { + t.Fatal(err) + } + + f := hldr.Frame("i", "f") + if value, exists, err := f.FieldValue(10, "field0"); err != nil { + t.Fatal(err) + } else if !exists { + t.Fatal("expected value to exist") + } else if value != 25 { + t.Fatal("unexpected value: %v", value) + } + + if value, exists, err := f.FieldValue(10, "field1"); err != nil { + t.Fatal(err) + } else if !exists { + t.Fatal("expected value to exist") + } else if value != 2 { + t.Fatal("unexpected value: %v", value) + } + + if value, exists, err := f.FieldValue(100, "field0"); err != nil { + t.Fatal(err) + } else if !exists { + t.Fatal("expected value to exist") + } else if value != 10 { + t.Fatal("unexpected value: %v", value) + } + }) + + t.Run("", func(t *testing.T) { + hldr := test.MustOpenHolder() + defer hldr.Close() + index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) + if _, err := index.CreateFrameIfNotExists("f", pilosa.FrameOptions{ + RangeEnabled: true, + Fields: []*pilosa.Field{ + {Name: "field0", Type: pilosa.FieldTypeInt, Min: 0, Max: 100}, + }, + }); err != nil { + t.Fatal(err) + } + + t.Run("ErrFrameRequired", func(t *testing.T) { + e := test.NewExecutor(hldr.Holder, test.NewCluster(1)) + if _, err := e.Execute(context.Background(), "i", test.MustParse(`SetFieldValue(columnID=10, field0=100)`), nil, nil); err == nil || err.Error() != `SetFieldValue() frame required` { + t.Fatalf("unexpected error: %s", err) + } + }) + + t.Run("ErrColumnFieldRequired", func(t *testing.T) { + e := test.NewExecutor(hldr.Holder, test.NewCluster(1)) + if _, err := e.Execute(context.Background(), "i", test.MustParse(`SetFieldValue(invalid_column_name=10, frame=f, field0=100)`), nil, nil); err == nil || err.Error() != `SetFieldValue() column field 'columnID' required` { + t.Fatalf("unexpected error: %s", err) + } + }) + + t.Run("ErrColumnFieldValue", func(t *testing.T) { + e := test.NewExecutor(hldr.Holder, test.NewCluster(1)) + if _, err := e.Execute(context.Background(), "i", test.MustParse(`SetFieldValue(invalid_column_name="bad_column", frame=f, field0=100)`), nil, nil); err == nil || err.Error() != `SetFieldValue() column field 'columnID' required` { + t.Fatalf("unexpected error: %s", err) + } + }) + + t.Run("ErrInvalidFieldValueType", func(t *testing.T) { + e := test.NewExecutor(hldr.Holder, test.NewCluster(1)) + if _, err := e.Execute(context.Background(), "i", test.MustParse(`SetFieldValue(columnID=10, frame=f, field0="hello")`), nil, nil); err == nil || err.Error() != `invalid field value type` { + t.Fatalf("unexpected error: %s", err) + } + }) + }) +} + // Ensure a SetRowAttrs() query can be executed. func TestExecutor_Execute_SetRowAttrs(t *testing.T) { hldr := test.MustOpenHolder() diff --git a/pilosa.go b/pilosa.go index a334a2be1..702eaa9e6 100644 --- a/pilosa.go +++ b/pilosa.go @@ -43,6 +43,7 @@ var ( ErrInverseRangeNotAllowed = errors.New("inverse range not allowed") ErrRangeCacheNotAllowed = errors.New("range cache not allowed") ErrFrameFieldsNotAllowed = errors.New("frame fields not allowed") + ErrInvalidFieldValueType = errors.New("invalid field value type") ErrFieldValueTooLow = errors.New("field value too low") ErrFieldValueTooHigh = errors.New("field value too high")