mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-10-06 19:07:50 +00:00
Merge pull request #721 from benbjohnson/618-pql-field-values
Add PQL support for field values.
This commit is contained in:
commit
c55a405ac5
3 changed files with 171 additions and 0 deletions
73
executor.go
73
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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -44,6 +44,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")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue