Skip to content

Commit

Permalink
Allow to list keys with no min or max boundaries (#434)
Browse files Browse the repository at this point in the history
  • Loading branch information
merlimat authored Jan 25, 2024
1 parent af126a6 commit cca6f66
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 148 deletions.
16 changes: 2 additions & 14 deletions cmd/client/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,23 +118,11 @@ func TestClientCmd(t *testing.T) {
"Error: binary flag was set when config is being sourced from stdin",
},
{"list-none", []string{"list", "--key-min", "XXX", "--key-max", "XXY"}, "", nil,
"\\{\"keys\":\\[\\]\\}",
"^$",
},
{"list-all", []string{"list", "--key-min", "a", "--key-max", "z"}, "", nil,
"\\{\"keys\":\\[\"k-put\",\"k-put-binary-ok\"\\]\\}",
"^$",
},
{"list-no-minimum", []string{"list", "--key-max", "XXY"}, "", list.ErrExpectedRangeInconsistent,
".*",
"Error: inconsistent flags; min and max flags must be in pairs",
},
{"list-no-maximum", []string{"list", "--key-min", "XXX"}, "", list.ErrExpectedRangeInconsistent,
".*",
"Error: inconsistent flags; min and max flags must be in pairs",
},
{"list-stdin", []string{"list"}, "{\"key_minimum\":\"j\",\"key_maximum\":\"l\"}\n{\"key_minimum\":\"a\",\"key_maximum\":\"b\"}\n", nil,
"\\{\"keys\":\\[\"k-put\",\"k-put-binary-ok\"\\]\\}",
{"list-all", []string{"list", "--key-min", "a", "--key-max", "z"}, "", nil,
"k-put\nk-put-binary-ok\n",
"^$",
},
{"delete", []string{"delete", "-k", "k-put-binary-ok"}, "", nil,
Expand Down
8 changes: 8 additions & 0 deletions cmd/client/common/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ func writeOutputCh(out io.Writer, valuesCh <-chan any) {
}

func writeOutput(out io.Writer, value any) {
if sl, ok := value.([]string); ok {
for _, s := range sl {
_, _ = out.Write([]byte(s))
_, _ = out.Write([]byte("\n"))
}
return
}

b, err := json.Marshal(value)
if err != nil {
panic(err)
Expand Down
50 changes: 15 additions & 35 deletions cmd/client/list/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ package list
import (
"context"
"encoding/json"
"errors"
"io"

"github.com/spf13/cobra"

Expand All @@ -28,30 +26,27 @@ import (

var (
Config = flags{}

ErrExpectedRangeInconsistent = errors.New("inconsistent flags; min and max flags must be in pairs")
)

type flags struct {
keyMinimums []string
keyMaximums []string
keyMin string
keyMax string
}

func (flags *flags) Reset() {
flags.keyMinimums = nil
flags.keyMaximums = nil
flags.keyMin = ""
flags.keyMin = ""
}

func init() {
Cmd.Flags().StringSliceVar(&Config.keyMinimums, "key-min", []string{}, "Key range minimum (inclusive)")
Cmd.Flags().StringSliceVar(&Config.keyMaximums, "key-max", []string{}, "Key range maximum (exclusive)")
Cmd.MarkFlagsRequiredTogether("key-min", "key-max")
Cmd.Flags().StringVar(&Config.keyMin, "key-min", "", "Key range minimum (inclusive)")
Cmd.Flags().StringVar(&Config.keyMax, "key-max", "", "Key range maximum (exclusive)")
}

var Cmd = &cobra.Command{
Use: "list",
Short: "List keys",
Long: `List keys that fall within the given key ranges.`,
Long: `List keys that fall within the given key range.`,
Args: cobra.NoArgs,
RunE: exec,
}
Expand All @@ -61,23 +56,14 @@ func exec(cmd *cobra.Command, _ []string) error {
defer func() {
loop.Complete()
}()
return _exec(Config, cmd.InOrStdin(), loop)
return _exec(Config, loop)
}

func _exec(flags flags, in io.Reader, queue common.QueryQueue) error {
if len(flags.keyMinimums) != len(flags.keyMaximums) {
return ErrExpectedRangeInconsistent
}
if len(flags.keyMinimums) > 0 {
for i, n := range flags.keyMinimums {
queue.Add(Query{
KeyMinimum: n,
KeyMaximum: flags.keyMaximums[i],
})
}
} else {
common.ReadStdin(in, Query{}, queue)
}
func _exec(flags flags, queue common.QueryQueue) error {
queue.Add(Query{
KeyMinimum: flags.keyMin,
KeyMaximum: flags.keyMax,
})
return nil
}

Expand Down Expand Up @@ -118,19 +104,13 @@ func (call Call) Complete() <-chan any {
Err: result.Err.Error(),
}
} else {
ch <- Output{
Keys: result.Keys,
}
ch <- result.Keys
}
}
if emptyOutput {
ch <- Output{Keys: make([]string, 0)}
ch <- []string{}
}
close(ch)
}()
return ch
}

type Output struct {
Keys []string `json:"keys"`
}
107 changes: 29 additions & 78 deletions cmd/client/list/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
package list

import (
"bytes"
"encoding/json"
"errors"
"testing"

Expand All @@ -29,24 +27,22 @@ import (

func TestCobra(t *testing.T) {
for _, test := range []struct {
name string
args []string
expectedErr error
expectedKeyMinimums []string
expectedKeyMaximums []string
name string
args []string
expectedErr error
expectedKeyMin string
expectedKeyMax string
}{
{"range", []string{"--key-min", "x", "--key-max", "y"}, nil, []string{"x"}, []string{"y"}},
{"ranges", []string{"--key-min", "x1", "--key-max", "y1", "--key-min", "x2", "--key-max", "y2"}, nil, []string{"x1", "x2"}, []string{"y1", "y2"}},
{"stdin", []string{}, nil, nil, nil},
{"range", []string{"--key-min", "x", "--key-max", "y"}, nil, "x", "y"},
} {
t.Run(test.name, func(t *testing.T) {
Config = flags{}
Cmd.SetArgs(test.args)
invoked := false
Cmd.RunE = func(cmd *cobra.Command, args []string) error {
invoked = true
assert.Equal(t, test.expectedKeyMinimums, Config.keyMinimums)
assert.Equal(t, test.expectedKeyMaximums, Config.keyMaximums)
assert.Equal(t, test.expectedKeyMin, Config.keyMin)
assert.Equal(t, test.expectedKeyMax, Config.keyMax)
assert.True(t, invoked)
return nil
}
Expand All @@ -68,58 +64,46 @@ func Test_exec(t *testing.T) {
{"range",
"",
flags{
keyMinimums: []string{"a"},
keyMaximums: []string{"b"},
keyMin: "a",
keyMax: "b",
},
nil,
[]common.Query{Query{
KeyMinimum: "a",
KeyMaximum: "b",
}}},
{"ranges",
{"range-no-min",
"",
flags{
keyMinimums: []string{"a", "x"},
keyMaximums: []string{"b", "y"},
keyMax: "b",
},
nil,
[]common.Query{Query{
KeyMinimum: "a",
KeyMinimum: "",
KeyMaximum: "b",
}, Query{
KeyMinimum: "x",
KeyMaximum: "y",
}}},
{"stdin",
"{\"key_minimum\":\"a\",\"key_maximum\":\"b\"}\n{\"key_minimum\":\"x\",\"key_maximum\":\"y\"}\n",
flags{},
{"range-no-max",
"",
flags{
keyMin: "a",
},
nil,
[]common.Query{Query{
KeyMinimum: "a",
KeyMaximum: "b",
}, Query{
KeyMinimum: "x",
KeyMaximum: "y",
KeyMaximum: "",
}}},
{"range-no-min",
"",
flags{
keyMaximums: []string{"b"},
},
ErrExpectedRangeInconsistent,
nil},
{"range-no-max",
{"range-no-limit",
"",
flags{
keyMaximums: []string{"b"},
},
ErrExpectedRangeInconsistent,
nil},
flags{},
nil,
[]common.Query{Query{
KeyMinimum: "",
KeyMaximum: "",
}}},
} {
t.Run(test.name, func(t *testing.T) {
in := bytes.NewBufferString(test.stdin)
queue := fakeQueryQueue{}
err := _exec(test.flags, in, &queue)
err := _exec(test.flags, &queue)
assert.Equal(t, test.expectedQueries, queue.queries)
assert.ErrorIs(t, err, test.expectedErr)
})
Expand Down Expand Up @@ -147,35 +131,6 @@ func TestInputUnmarshal(t *testing.T) {
}
}

func TestOutputMarshal(t *testing.T) {
for _, test := range []struct {
name string
output Output
expected string
}{
{"some",
Output{
Keys: []string{"a", "b"},
},
"{\"keys\":[\"a\",\"b\"]}",
},
{"none",
Output{
Keys: []string{},
},
"{\"keys\":[]}",
},
} {
t.Run(test.name, func(t *testing.T) {
result, err := json.Marshal(test.output)
if err != nil {
panic(err)
}
assert.Equal(t, test.expected, string(result))
})
}
}

func TestCall_Complete(t *testing.T) {
tests := []struct {
name string
Expand All @@ -186,17 +141,13 @@ func TestCall_Complete(t *testing.T) {
"keys",
oxia.ListResult{
Keys: []string{"a", "b"},
}, Output{
Keys: []string{"a", "b"},
},
}, []string{"a", "b"},
},
{
"empty",
oxia.ListResult{
Keys: []string{},
}, Output{
Keys: []string{},
},
}, []string{},
},
{
"error",
Expand Down
37 changes: 19 additions & 18 deletions server/kv/kv_pebble.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,22 +370,18 @@ func (p *Pebble) Get(key string) ([]byte, io.Closer, error) {
}

func (p *Pebble) KeyRangeScan(lowerBound, upperBound string) (KeyIterator, error) {
pbit, err := p.db.NewIter(&pebble.IterOptions{
LowerBound: []byte(lowerBound),
UpperBound: []byte(upperBound),
})
if err != nil {
return nil, err
}
pbit.SeekGE([]byte(lowerBound))
return &PebbleIterator{p, pbit}, nil
return p.RangeScan(lowerBound, upperBound)
}

func (p *Pebble) KeyRangeScanReverse(lowerBound, upperBound string) (ReverseKeyIterator, error) {
pbit, err := p.db.NewIter(&pebble.IterOptions{
LowerBound: []byte(lowerBound),
UpperBound: []byte(upperBound),
})
opts := &pebble.IterOptions{}
if lowerBound != "" {
opts.LowerBound = []byte(lowerBound)
}
if upperBound != "" {
opts.UpperBound = []byte(upperBound)
}
pbit, err := p.db.NewIter(opts)
if err != nil {
return nil, err
}
Expand All @@ -394,14 +390,19 @@ func (p *Pebble) KeyRangeScanReverse(lowerBound, upperBound string) (ReverseKeyI
}

func (p *Pebble) RangeScan(lowerBound, upperBound string) (KeyValueIterator, error) {
pbit, err := p.db.NewIter(&pebble.IterOptions{
LowerBound: []byte(lowerBound),
UpperBound: []byte(upperBound),
})
opts := &pebble.IterOptions{}
if lowerBound != "" {
opts.LowerBound = []byte(lowerBound)
}
if upperBound != "" {
opts.UpperBound = []byte(upperBound)
}
pbit, err := p.db.NewIter(opts)
if err != nil {
return nil, err
}
pbit.SeekGE([]byte(lowerBound))

pbit.First()
return &PebbleIterator{p, pbit}, nil
}

Expand Down
Loading

0 comments on commit cca6f66

Please sign in to comment.