mirror of
				https://github.com/nalgeon/redka.git
				synced 2025-10-31 03:16:28 +08:00 
			
		
		
		
	refactor: command - better encapsulation and simpler tests
This commit is contained in:
		| @@ -14,25 +14,6 @@ import ( | |||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func MustParse[T redis.Cmd](s string) T { |  | ||||||
| 	parts := strings.Split(s, " ") |  | ||||||
| 	args := BuildArgs(parts[0], parts[1:]...) |  | ||||||
| 	cmd, err := Parse(args) |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 	return cmd.(T) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func BuildArgs(name string, args ...string) [][]byte { |  | ||||||
| 	rargs := make([][]byte, len(args)+1) |  | ||||||
| 	rargs[0] = []byte(name) |  | ||||||
| 	for i, arg := range args { |  | ||||||
| 		rargs[i+1] = []byte(arg) |  | ||||||
| 	} |  | ||||||
| 	return rargs |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Parse parses a text representation of a command into a Cmd. | // Parse parses a text representation of a command into a Cmd. | ||||||
| func Parse(args [][]byte) (redis.Cmd, error) { | func Parse(args [][]byte) (redis.Cmd, error) { | ||||||
| 	name := strings.ToLower(string(args[0])) | 	name := strings.ToLower(string(args[0])) | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| package conn_test | package conn | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|   | |||||||
| @@ -12,13 +12,13 @@ import ( | |||||||
| // https://redis.io/commands/echo | // https://redis.io/commands/echo | ||||||
| type Echo struct { | type Echo struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Parts []string | 	parts []string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseEcho(b redis.BaseCmd) (*Echo, error) { | func ParseEcho(b redis.BaseCmd) (*Echo, error) { | ||||||
| 	cmd := &Echo{BaseCmd: b} | 	cmd := &Echo{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.Strings(&cmd.Parts), | 		parser.Strings(&cmd.parts), | ||||||
| 	).Required(1).Run(cmd.Args()) | 	).Required(1).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return cmd, err | 		return cmd, err | ||||||
| @@ -27,7 +27,7 @@ func ParseEcho(b redis.BaseCmd) (*Echo, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Echo) Run(w redis.Writer, _ redis.Redka) (any, error) { | func (c *Echo) Run(w redis.Writer, _ redis.Redka) (any, error) { | ||||||
| 	out := strings.Join(c.Parts, " ") | 	out := strings.Join(c.parts, " ") | ||||||
| 	w.WriteAny(out) | 	w.WriteAny(out) | ||||||
| 	return out, nil | 	return out, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,47 +1,42 @@ | |||||||
| package conn_test | package conn | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/conn" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestEchoParse(t *testing.T) { | func TestEchoParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		args [][]byte | ||||||
| 		want []string | 		want []string | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "echo", | 			cmd:  "echo", | ||||||
| 			args: command.BuildArgs("echo"), |  | ||||||
| 			want: []string{}, | 			want: []string{}, | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "echo hello", | 			cmd:  "echo hello", | ||||||
| 			args: command.BuildArgs("echo", "hello"), |  | ||||||
| 			want: []string{"hello"}, | 			want: []string{"hello"}, | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "echo one two", | 			cmd:  "echo one two", | ||||||
| 			args: command.BuildArgs("echo", "one", "two"), |  | ||||||
| 			want: []string{"one", "two"}, | 			want: []string{"one", "two"}, | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseEcho, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*conn.Echo).Parts, test.want) | 				testx.AssertEqual(t, cmd.parts, test.want) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -52,29 +47,27 @@ func TestEchoExec(t *testing.T) { | |||||||
| 	defer db.Close() | 	defer db.Close() | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		cmd  *conn.Echo | 		res any | ||||||
| 		res  any | 		out string | ||||||
| 		out  string |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "echo hello", | 			cmd: "echo hello", | ||||||
| 			cmd:  command.MustParse[*conn.Echo]("echo hello"), | 			res: "hello", | ||||||
| 			res:  "hello", | 			out: "hello", | ||||||
| 			out:  "hello", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "echo one two", | 			cmd: "echo one two", | ||||||
| 			cmd:  command.MustParse[*conn.Echo]("echo one two"), | 			res: "one two", | ||||||
| 			res:  "one two", | 			out: "one two", | ||||||
| 			out:  "one two", |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := test.cmd.Run(conn, red) | 			cmd := redis.MustParse(ParseEcho, test.cmd) | ||||||
|  | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| 			testx.AssertEqual(t, res, test.res) | 			testx.AssertEqual(t, res, test.res) | ||||||
| 			testx.AssertEqual(t, conn.Out(), test.out) | 			testx.AssertEqual(t, conn.Out(), test.out) | ||||||
|   | |||||||
| @@ -1,41 +1,37 @@ | |||||||
| package conn | package conn | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"strings" | 	"github.com/nalgeon/redka/internal/parser" | ||||||
|  | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/parser" | ) | ||||||
| 	"github.com/nalgeon/redka/internal/redis" |  | ||||||
| ) | const ( | ||||||
|  | 	PONG = "PONG" | ||||||
| const ( | ) | ||||||
| 	PONG = "PONG" |  | ||||||
| ) | // Returns the server's liveliness response. | ||||||
|  | // https://redis.io/commands/ping | ||||||
| // Returns PONG if no argument is provided, otherwise return a copy of the argument as a bulk | type Ping struct { | ||||||
| // https://redis.io/commands/ping | 	redis.BaseCmd | ||||||
| type Ping struct { | 	message string | ||||||
| 	redis.BaseCmd | } | ||||||
| 	Parts []string |  | ||||||
| } | func ParsePing(b redis.BaseCmd) (*Ping, error) { | ||||||
|  | 	cmd := &Ping{BaseCmd: b} | ||||||
|  | 	err := parser.New( | ||||||
| func ParsePing(b redis.BaseCmd) (*Ping, error) { | 		parser.String(&cmd.message), | ||||||
| 	cmd := &Ping{BaseCmd: b} | 	).Required(0).Run(cmd.Args()) | ||||||
| 	err := parser.New( | 	if err != nil { | ||||||
| 		parser.Strings(&cmd.Parts), | 		return cmd, err | ||||||
| 	).Required(0).Run(cmd.Args()) | 	} | ||||||
| 	if err != nil { | 	return cmd, nil | ||||||
| 		return cmd, err | } | ||||||
| 	} |  | ||||||
| 	return cmd, nil | func (c *Ping) Run(w redis.Writer, _ redis.Redka) (any, error) { | ||||||
| } | 	if c.message == "" { | ||||||
|  | 		w.WriteAny(PONG) | ||||||
| func (c *Ping) Run(w redis.Writer, _ redis.Redka) (any, error) { | 		return PONG, nil | ||||||
| 	if len(c.Parts) == 0 { | 	} | ||||||
| 		w.WriteAny(PONG) | 	w.WriteBulkString(c.message) | ||||||
| 		return PONG, nil | 	return c.message, nil | ||||||
| 	} | } | ||||||
| 	out := strings.Join(c.Parts, " ") |  | ||||||
| 	w.WriteAny(out) |  | ||||||
| 	return out, nil |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,89 +1,75 @@ | |||||||
| package conn_test | package conn | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/command/conn" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | ) | ||||||
| 	"github.com/nalgeon/redka/internal/testx" |  | ||||||
| ) | func TestPingParse(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
| func TestPingParse(t *testing.T) { | 		cmd  string | ||||||
| 	tests := []struct { | 		want string | ||||||
| 		name string | 		err  error | ||||||
| 		args [][]byte | 	}{ | ||||||
| 		want []string | 		{ | ||||||
| 		err  error | 			cmd:  "ping", | ||||||
| 	}{ | 			want: "", | ||||||
| 		{ | 			err:  nil, | ||||||
| 			name: "ping", | 		}, | ||||||
| 			args: command.BuildArgs("ping"), | 		{ | ||||||
| 			want: []string(nil), | 			cmd:  "ping hello", | ||||||
| 			err:  nil, | 			want: "hello", | ||||||
| 		}, | 			err:  nil, | ||||||
| 		{ | 		}, | ||||||
| 			name: "ping hello", | 		{ | ||||||
| 			args: command.BuildArgs("ping", "hello"), | 			cmd:  "ping one two", | ||||||
| 			want: []string{"hello"}, | 			want: "", | ||||||
| 			err:  nil, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 	} | ||||||
| 			name: "ping one two", |  | ||||||
| 			args: command.BuildArgs("ping", "one", "two"), | 	for _, test := range tests { | ||||||
| 			want: []string{"one", "two"}, | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			err:  nil, | 			cmd, err := redis.Parse(ParsePing, test.cmd) | ||||||
| 		}, | 			testx.AssertEqual(t, err, test.err) | ||||||
| 	} | 			if err == nil { | ||||||
|  | 				testx.AssertEqual(t, cmd.message, test.want) | ||||||
| 	for _, test := range tests { | 			} | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		}) | ||||||
| 			cmd, err := command.Parse(test.args) | 	} | ||||||
| 			testx.AssertEqual(t, err, test.err) | } | ||||||
| 			if err == nil { |  | ||||||
| 				testx.AssertEqual(t, cmd.(*conn.Ping).Parts, test.want) | func TestPingExec(t *testing.T) { | ||||||
| 			} | 	db, red := getDB(t) | ||||||
| 		}) | 	defer db.Close() | ||||||
| 	} |  | ||||||
| } | 	tests := []struct { | ||||||
|  | 		cmd string | ||||||
| func TestPingExec(t *testing.T) { | 		res any | ||||||
| 	db, red := getDB(t) | 		out string | ||||||
| 	defer db.Close() | 	}{ | ||||||
|  | 		{ | ||||||
| 	tests := []struct { | 			cmd: "ping", | ||||||
| 		name string | 			res: "PONG", | ||||||
| 		cmd  *conn.Ping | 			out: "PONG", | ||||||
| 		res  any | 		}, | ||||||
| 		out  string | 		{ | ||||||
| 	}{ | 			cmd: "ping hello", | ||||||
| 		{ | 			res: "hello", | ||||||
| 			name: "ping", | 			out: "hello", | ||||||
| 			cmd:  command.MustParse[*conn.Ping]("ping"), | 		}, | ||||||
| 			res:  "PONG", | 	} | ||||||
| 			out:  "PONG", |  | ||||||
| 		}, | 	for _, test := range tests { | ||||||
| 		{ | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			name: "ping hello", | 			conn := redis.NewFakeConn() | ||||||
| 			cmd:  command.MustParse[*conn.Ping]("ping hello"), | 			cmd := redis.MustParse(ParsePing, test.cmd) | ||||||
| 			res:  "hello", | 			res, err := cmd.Run(conn, red) | ||||||
| 			out:  "hello", | 			testx.AssertNoErr(t, err) | ||||||
| 		}, | 			testx.AssertEqual(t, res, test.res) | ||||||
| 		{ | 			testx.AssertEqual(t, conn.Out(), test.out) | ||||||
| 			name: "ping one two", | 		}) | ||||||
| 			cmd:  command.MustParse[*conn.Ping]("ping one two"), | 	} | ||||||
| 			res:  "one two", | } | ||||||
| 			out:  "one two", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, test := range tests { |  | ||||||
| 		t.Run(test.name, func(t *testing.T) { |  | ||||||
| 			conn := redis.NewFakeConn() |  | ||||||
| 			res, err := test.cmd.Run(conn, red) |  | ||||||
| 			testx.AssertNoErr(t, err) |  | ||||||
| 			testx.AssertEqual(t, res, test.res) |  | ||||||
| 			testx.AssertEqual(t, conn.Out(), test.out) |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|   | |||||||
| @@ -11,15 +11,15 @@ import ( | |||||||
| // https://redis.io/commands/hdel | // https://redis.io/commands/hdel | ||||||
| type HDel struct { | type HDel struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key    string | 	key    string | ||||||
| 	Fields []string | 	fields []string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHDel(b redis.BaseCmd) (*HDel, error) { | func ParseHDel(b redis.BaseCmd) (*HDel, error) { | ||||||
| 	cmd := &HDel{BaseCmd: b} | 	cmd := &HDel{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Strings(&cmd.Fields), | 		parser.Strings(&cmd.fields), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -28,7 +28,7 @@ func ParseHDel(b redis.BaseCmd) (*HDel, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HDel) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HDel) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	count, err := red.Hash().Delete(cmd.Key, cmd.Fields...) | 	count, err := red.Hash().Delete(cmd.key, cmd.fields...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,46 +1,39 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestHDelParse(t *testing.T) { | func TestHDelParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name   string | 		cmd    string | ||||||
| 		args   [][]byte |  | ||||||
| 		key    string | 		key    string | ||||||
| 		fields []string | 		fields []string | ||||||
| 		err    error | 		err    error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hdel", | 			cmd:    "hdel", | ||||||
| 			args:   command.BuildArgs("hdel"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			fields: nil, | 			fields: nil, | ||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hdel person", | 			cmd:    "hdel person", | ||||||
| 			args:   command.BuildArgs("hdel", "person"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			fields: nil, | 			fields: nil, | ||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hdel person name", | 			cmd:    "hdel person name", | ||||||
| 			args:   command.BuildArgs("hdel", "person", "name"), |  | ||||||
| 			key:    "person", | 			key:    "person", | ||||||
| 			fields: []string{"name"}, | 			fields: []string{"name"}, | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hdel person name age", | 			cmd:    "hdel person name age", | ||||||
| 			args:   command.BuildArgs("hdel", "person", "name", "age"), |  | ||||||
| 			key:    "person", | 			key:    "person", | ||||||
| 			fields: []string{"name", "age"}, | 			fields: []string{"name", "age"}, | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| @@ -48,13 +41,12 @@ func TestHDelParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHDel, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HDel) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.key) | 				testx.AssertEqual(t, cmd.fields, test.fields) | ||||||
| 				testx.AssertEqual(t, cm.Fields, test.fields) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -68,7 +60,7 @@ func TestHDelExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HDel]("hdel person name") | 		cmd := redis.MustParse(ParseHDel, "hdel person name") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -89,7 +81,7 @@ func TestHDelExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
| 		_, _ = db.Hash().Set("person", "happy", true) | 		_, _ = db.Hash().Set("person", "happy", true) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HDel]("hdel person name happy city") | 		cmd := redis.MustParse(ParseHDel, "hdel person name happy city") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -111,7 +103,7 @@ func TestHDelExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HDel]("hdel person name age") | 		cmd := redis.MustParse(ParseHDel, "hdel person name age") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,8 +7,8 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/hexists | // https://redis.io/commands/hexists | ||||||
| type HExists struct { | type HExists struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Field string | 	field string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHExists(b redis.BaseCmd) (*HExists, error) { | func ParseHExists(b redis.BaseCmd) (*HExists, error) { | ||||||
| @@ -16,13 +16,13 @@ func ParseHExists(b redis.BaseCmd) (*HExists, error) { | |||||||
| 	if len(cmd.Args()) != 2 { | 	if len(cmd.Args()) != 2 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	cmd.Field = string(cmd.Args()[1]) | 	cmd.field = string(cmd.Args()[1]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HExists) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HExists) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	ok, err := red.Hash().Exists(cmd.Key, cmd.Field) | 	ok, err := red.Hash().Exists(cmd.key, cmd.field) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,46 +1,39 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestHExistsParse(t *testing.T) { | func TestHExistsParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name  string | 		cmd   string | ||||||
| 		args  [][]byte |  | ||||||
| 		key   string | 		key   string | ||||||
| 		field string | 		field string | ||||||
| 		err   error | 		err   error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hexists", | 			cmd:   "hexists", | ||||||
| 			args:  command.BuildArgs("hexists"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hexists person", | 			cmd:   "hexists person", | ||||||
| 			args:  command.BuildArgs("hexists", "person"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hexists person name", | 			cmd:   "hexists person name", | ||||||
| 			args:  command.BuildArgs("hexists", "person", "name"), |  | ||||||
| 			key:   "person", | 			key:   "person", | ||||||
| 			field: "name", | 			field: "name", | ||||||
| 			err:   nil, | 			err:   nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hexists person name age", | 			cmd:   "hexists person name age", | ||||||
| 			args:  command.BuildArgs("hexists", "person", "name", "age"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| @@ -48,13 +41,12 @@ func TestHExistsParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHExists, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HExists) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.key) | 				testx.AssertEqual(t, cmd.field, test.field) | ||||||
| 				testx.AssertEqual(t, cm.Field, test.field) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -67,7 +59,7 @@ func TestHExistsExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HExists]("hexists person name") | 		cmd := redis.MustParse(ParseHExists, "hexists person name") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -81,7 +73,7 @@ func TestHExistsExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HExists]("hexists person age") | 		cmd := redis.MustParse(ParseHExists, "hexists person age") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -93,7 +85,7 @@ func TestHExistsExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HExists]("hexists person name") | 		cmd := redis.MustParse(ParseHExists, "hexists person name") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,8 +10,8 @@ import ( | |||||||
| // https://redis.io/commands/hget | // https://redis.io/commands/hget | ||||||
| type HGet struct { | type HGet struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Field string | 	field string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHGet(b redis.BaseCmd) (*HGet, error) { | func ParseHGet(b redis.BaseCmd) (*HGet, error) { | ||||||
| @@ -19,13 +19,13 @@ func ParseHGet(b redis.BaseCmd) (*HGet, error) { | |||||||
| 	if len(cmd.Args()) != 2 { | 	if len(cmd.Args()) != 2 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	cmd.Field = string(cmd.Args()[1]) | 	cmd.field = string(cmd.Args()[1]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HGet) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HGet) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	val, err := red.Hash().Get(cmd.Key, cmd.Field) | 	val, err := red.Hash().Get(cmd.key, cmd.field) | ||||||
| 	if err == core.ErrNotFound { | 	if err == core.ErrNotFound { | ||||||
| 		w.WriteNull() | 		w.WriteNull() | ||||||
| 		return val, nil | 		return val, nil | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,36 +10,31 @@ import ( | |||||||
|  |  | ||||||
| func TestHGetParse(t *testing.T) { | func TestHGetParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name  string | 		cmd   string | ||||||
| 		args  [][]byte |  | ||||||
| 		key   string | 		key   string | ||||||
| 		field string | 		field string | ||||||
| 		err   error | 		err   error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hget", | 			cmd:   "hget", | ||||||
| 			args:  command.BuildArgs("hget"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hget person", | 			cmd:   "hget person", | ||||||
| 			args:  command.BuildArgs("hget", "person"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hget person name", | 			cmd:   "hget person name", | ||||||
| 			args:  command.BuildArgs("hget", "person", "name"), |  | ||||||
| 			key:   "person", | 			key:   "person", | ||||||
| 			field: "name", | 			field: "name", | ||||||
| 			err:   nil, | 			err:   nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hget person name age", | 			cmd:   "hget person name age", | ||||||
| 			args:  command.BuildArgs("hget", "person", "name", "age"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| @@ -49,13 +42,12 @@ func TestHGetParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHGet, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HGet) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.key) | 				testx.AssertEqual(t, cmd.field, test.field) | ||||||
| 				testx.AssertEqual(t, cm.Field, test.field) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -68,7 +60,7 @@ func TestHGetExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HGet]("hget person name") | 		cmd := redis.MustParse(ParseHGet, "hget person name") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -82,7 +74,7 @@ func TestHGetExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HGet]("hget person age") | 		cmd := redis.MustParse(ParseHGet, "hget person age") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -94,7 +86,7 @@ func TestHGetExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HGet]("hget person name") | 		cmd := redis.MustParse(ParseHGet, "hget person name") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/hgetall | // https://redis.io/commands/hgetall | ||||||
| type HGetAll struct { | type HGetAll struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key string | 	key string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHGetAll(b redis.BaseCmd) (*HGetAll, error) { | func ParseHGetAll(b redis.BaseCmd) (*HGetAll, error) { | ||||||
| @@ -15,12 +15,12 @@ func ParseHGetAll(b redis.BaseCmd) (*HGetAll, error) { | |||||||
| 	if len(cmd.Args()) != 1 { | 	if len(cmd.Args()) != 1 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HGetAll) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HGetAll) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	items, err := red.Hash().Items(cmd.Key) | 	items, err := red.Hash().Items(cmd.key) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,38 +10,33 @@ import ( | |||||||
|  |  | ||||||
| func TestHGetAllParse(t *testing.T) { | func TestHGetAllParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		args [][]byte | 		key string | ||||||
| 		key  string | 		err error | ||||||
| 		err  error |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "hgetall", | 			cmd: "hgetall", | ||||||
| 			args: command.BuildArgs("hgetall"), | 			key: "", | ||||||
| 			key:  "", | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hgetall person", | 			cmd: "hgetall person", | ||||||
| 			args: command.BuildArgs("hgetall", "person"), | 			key: "person", | ||||||
| 			key:  "person", | 			err: nil, | ||||||
| 			err:  nil, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hgetall person name", | 			cmd: "hgetall person name", | ||||||
| 			args: command.BuildArgs("hgetall", "person", "name"), | 			key: "", | ||||||
| 			key:  "", | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHGetAll, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HGetAll) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.key) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -57,7 +50,7 @@ func TestHGetAllExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HGetAll]("hgetall person") | 		cmd := redis.MustParse(ParseHGetAll, "hgetall person") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -73,7 +66,7 @@ func TestHGetAllExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HGetAll]("hgetall person") | 		cmd := redis.MustParse(ParseHGetAll, "hgetall person") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,17 +11,17 @@ import ( | |||||||
| // https://redis.io/commands/hincrby | // https://redis.io/commands/hincrby | ||||||
| type HIncrBy struct { | type HIncrBy struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Field string | 	field string | ||||||
| 	Delta int | 	delta int | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHIncrBy(b redis.BaseCmd) (*HIncrBy, error) { | func ParseHIncrBy(b redis.BaseCmd) (*HIncrBy, error) { | ||||||
| 	cmd := &HIncrBy{BaseCmd: b} | 	cmd := &HIncrBy{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.String(&cmd.Field), | 		parser.String(&cmd.field), | ||||||
| 		parser.Int(&cmd.Delta), | 		parser.Int(&cmd.delta), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -30,7 +30,7 @@ func ParseHIncrBy(b redis.BaseCmd) (*HIncrBy, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HIncrBy) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HIncrBy) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	val, err := red.Hash().Incr(cmd.Key, cmd.Field, cmd.Delta) | 	val, err := red.Hash().Incr(cmd.key, cmd.field, cmd.delta) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,37 +10,32 @@ import ( | |||||||
|  |  | ||||||
| func TestHIncrByParse(t *testing.T) { | func TestHIncrByParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name  string | 		cmd   string | ||||||
| 		args  [][]byte |  | ||||||
| 		key   string | 		key   string | ||||||
| 		field string | 		field string | ||||||
| 		delta int | 		delta int | ||||||
| 		err   error | 		err   error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hincrby", | 			cmd:   "hincrby", | ||||||
| 			args:  command.BuildArgs("hincrby"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hincrby person", | 			cmd:   "hincrby person", | ||||||
| 			args:  command.BuildArgs("hincrby", "person"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hincrby person age", | 			cmd:   "hincrby person age", | ||||||
| 			args:  command.BuildArgs("hincrby", "person", "age"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hincrby person age 10", | 			cmd:   "hincrby person age 10", | ||||||
| 			args:  command.BuildArgs("hincrby", "person", "age", "10"), |  | ||||||
| 			key:   "person", | 			key:   "person", | ||||||
| 			field: "age", | 			field: "age", | ||||||
| 			delta: 10, | 			delta: 10, | ||||||
| @@ -51,14 +44,13 @@ func TestHIncrByParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHIncrBy, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HIncrBy) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.key) | 				testx.AssertEqual(t, cmd.field, test.field) | ||||||
| 				testx.AssertEqual(t, cm.Field, test.field) | 				testx.AssertEqual(t, cmd.delta, test.delta) | ||||||
| 				testx.AssertEqual(t, cm.Delta, test.delta) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -71,7 +63,7 @@ func TestHIncrByExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HIncrBy]("hincrby person age 10") | 		cmd := redis.MustParse(ParseHIncrBy, "hincrby person age 10") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -88,7 +80,7 @@ func TestHIncrByExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HIncrBy]("hincrby person age -10") | 		cmd := redis.MustParse(ParseHIncrBy, "hincrby person age -10") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -105,7 +97,7 @@ func TestHIncrByExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HIncrBy]("hincrby person age 10") | 		cmd := redis.MustParse(ParseHIncrBy, "hincrby person age 10") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -120,7 +112,7 @@ func TestHIncrByExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HIncrBy]("hincrby person age 10") | 		cmd := redis.MustParse(ParseHIncrBy, "hincrby person age 10") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,8 +1,6 @@ | |||||||
| package hash | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"strconv" |  | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/parser" | 	"github.com/nalgeon/redka/internal/parser" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| ) | ) | ||||||
| @@ -13,17 +11,17 @@ import ( | |||||||
| // https://redis.io/commands/hincrbyfloat | // https://redis.io/commands/hincrbyfloat | ||||||
| type HIncrByFloat struct { | type HIncrByFloat struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Field string | 	field string | ||||||
| 	Delta float64 | 	delta float64 | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHIncrByFloat(b redis.BaseCmd) (*HIncrByFloat, error) { | func ParseHIncrByFloat(b redis.BaseCmd) (*HIncrByFloat, error) { | ||||||
| 	cmd := &HIncrByFloat{BaseCmd: b} | 	cmd := &HIncrByFloat{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.String(&cmd.Field), | 		parser.String(&cmd.field), | ||||||
| 		parser.Float(&cmd.Delta), | 		parser.Float(&cmd.delta), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -32,11 +30,11 @@ func ParseHIncrByFloat(b redis.BaseCmd) (*HIncrByFloat, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HIncrByFloat) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HIncrByFloat) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	val, err := red.Hash().IncrFloat(cmd.Key, cmd.Field, cmd.Delta) | 	val, err := red.Hash().IncrFloat(cmd.key, cmd.field, cmd.delta) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	w.WriteBulkString(strconv.FormatFloat(val, 'f', -1, 64)) | 	redis.WriteFloat(w, val) | ||||||
| 	return val, nil | 	return val, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,37 +10,32 @@ import ( | |||||||
|  |  | ||||||
| func TestHIncrByFloatParse(t *testing.T) { | func TestHIncrByFloatParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name  string | 		cmd   string | ||||||
| 		args  [][]byte |  | ||||||
| 		key   string | 		key   string | ||||||
| 		field string | 		field string | ||||||
| 		delta float64 | 		delta float64 | ||||||
| 		err   error | 		err   error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hincrbyfloat", | 			cmd:   "hincrbyfloat", | ||||||
| 			args:  command.BuildArgs("hincrbyfloat"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hincrbyfloat person", | 			cmd:   "hincrbyfloat person", | ||||||
| 			args:  command.BuildArgs("hincrbyfloat", "person"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hincrbyfloat person age", | 			cmd:   "hincrbyfloat person age", | ||||||
| 			args:  command.BuildArgs("hincrbyfloat", "person", "age"), |  | ||||||
| 			key:   "", | 			key:   "", | ||||||
| 			field: "", | 			field: "", | ||||||
| 			err:   redis.ErrInvalidArgNum, | 			err:   redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "hincrbyfloat person age 10.5", | 			cmd:   "hincrbyfloat person age 10.5", | ||||||
| 			args:  command.BuildArgs("hincrbyfloat", "person", "age", "10.5"), |  | ||||||
| 			key:   "person", | 			key:   "person", | ||||||
| 			field: "age", | 			field: "age", | ||||||
| 			delta: 10.5, | 			delta: 10.5, | ||||||
| @@ -51,14 +44,13 @@ func TestHIncrByFloatParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHIncrByFloat, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HIncrByFloat) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.key) | 				testx.AssertEqual(t, cmd.field, test.field) | ||||||
| 				testx.AssertEqual(t, cm.Field, test.field) | 				testx.AssertEqual(t, cmd.delta, test.delta) | ||||||
| 				testx.AssertEqual(t, cm.Delta, test.delta) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -71,7 +63,7 @@ func TestHIncrByFloatExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HIncrByFloat]("hincrbyfloat person age 10.5") | 		cmd := redis.MustParse(ParseHIncrByFloat, "hincrbyfloat person age 10.5") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -88,7 +80,7 @@ func TestHIncrByFloatExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HIncrByFloat]("hincrbyfloat person age -10.5") | 		cmd := redis.MustParse(ParseHIncrByFloat, "hincrbyfloat person age -10.5") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -105,7 +97,7 @@ func TestHIncrByFloatExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HIncrByFloat]("hincrbyfloat person age 10.5") | 		cmd := redis.MustParse(ParseHIncrByFloat, "hincrbyfloat person age 10.5") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -120,7 +112,7 @@ func TestHIncrByFloatExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HIncrByFloat]("hincrbyfloat person age 10.5") | 		cmd := redis.MustParse(ParseHIncrByFloat, "hincrbyfloat person age 10.5") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/hkeys | // https://redis.io/commands/hkeys | ||||||
| type HKeys struct { | type HKeys struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key string | 	key string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHKeys(b redis.BaseCmd) (*HKeys, error) { | func ParseHKeys(b redis.BaseCmd) (*HKeys, error) { | ||||||
| @@ -15,12 +15,12 @@ func ParseHKeys(b redis.BaseCmd) (*HKeys, error) { | |||||||
| 	if len(cmd.Args()) != 1 { | 	if len(cmd.Args()) != 1 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HKeys) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HKeys) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	fields, err := red.Hash().Fields(cmd.Key) | 	fields, err := red.Hash().Fields(cmd.key) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,48 +1,41 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestHKeysParse(t *testing.T) { | func TestHKeysParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		args [][]byte | 		key string | ||||||
| 		key  string | 		err error | ||||||
| 		err  error |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "hkeys", | 			cmd: "hkeys", | ||||||
| 			args: command.BuildArgs("hkeys"), | 			key: "", | ||||||
| 			key:  "", | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hkeys person", | 			cmd: "hkeys person", | ||||||
| 			args: command.BuildArgs("hkeys", "person"), | 			key: "person", | ||||||
| 			key:  "person", | 			err: nil, | ||||||
| 			err:  nil, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hkeys person name", | 			cmd: "hkeys person name", | ||||||
| 			args: command.BuildArgs("hkeys", "person", "name"), | 			key: "", | ||||||
| 			key:  "", | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHKeys, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HKeys) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.key) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -56,7 +49,7 @@ func TestHKeysExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HKeys]("hkeys person") | 		cmd := redis.MustParse(ParseHKeys, "hkeys person") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -70,7 +63,7 @@ func TestHKeysExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HKeys]("hkeys person") | 		cmd := redis.MustParse(ParseHKeys, "hkeys person") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/hlen | // https://redis.io/commands/hlen | ||||||
| type HLen struct { | type HLen struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key string | 	key string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHLen(b redis.BaseCmd) (*HLen, error) { | func ParseHLen(b redis.BaseCmd) (*HLen, error) { | ||||||
| @@ -15,12 +15,12 @@ func ParseHLen(b redis.BaseCmd) (*HLen, error) { | |||||||
| 	if len(cmd.Args()) != 1 { | 	if len(cmd.Args()) != 1 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HLen) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HLen) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	count, err := red.Hash().Len(cmd.Key) | 	count, err := red.Hash().Len(cmd.key) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,48 +1,41 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestHLenParse(t *testing.T) { | func TestHLenParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		args [][]byte | 		key string | ||||||
| 		key  string | 		err error | ||||||
| 		err  error |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "hlen", | 			cmd: "hlen", | ||||||
| 			args: command.BuildArgs("hlen"), | 			key: "", | ||||||
| 			key:  "", | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hlen person", | 			cmd: "hlen person", | ||||||
| 			args: command.BuildArgs("hlen", "person"), | 			key: "person", | ||||||
| 			key:  "person", | 			err: nil, | ||||||
| 			err:  nil, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hlen person name", | 			cmd: "hlen person name", | ||||||
| 			args: command.BuildArgs("hlen", "person", "name"), | 			key: "", | ||||||
| 			key:  "", | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHLen, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HLen) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.key) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -56,7 +49,7 @@ func TestHLenExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HLen]("hlen person") | 		cmd := redis.MustParse(ParseHLen, "hlen person") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -68,7 +61,7 @@ func TestHLenExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HLen]("hlen person") | 		cmd := redis.MustParse(ParseHLen, "hlen person") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,15 +11,15 @@ import ( | |||||||
| // https://redis.io/commands/hmget | // https://redis.io/commands/hmget | ||||||
| type HMGet struct { | type HMGet struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key    string | 	key    string | ||||||
| 	Fields []string | 	fields []string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHMGet(b redis.BaseCmd) (*HMGet, error) { | func ParseHMGet(b redis.BaseCmd) (*HMGet, error) { | ||||||
| 	cmd := &HMGet{BaseCmd: b} | 	cmd := &HMGet{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Strings(&cmd.Fields), | 		parser.Strings(&cmd.fields), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -29,7 +29,7 @@ func ParseHMGet(b redis.BaseCmd) (*HMGet, error) { | |||||||
|  |  | ||||||
| func (cmd *HMGet) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HMGet) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	// Get the field-value map for requested fields. | 	// Get the field-value map for requested fields. | ||||||
| 	items, err := red.Hash().GetMany(cmd.Key, cmd.Fields...) | 	items, err := red.Hash().GetMany(cmd.key, cmd.fields...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -38,8 +38,8 @@ func (cmd *HMGet) Run(w redis.Writer, red redis.Redka) (any, error) { | |||||||
| 	// Build the result slice. | 	// Build the result slice. | ||||||
| 	// It will contain all values in the order of fields. | 	// It will contain all values in the order of fields. | ||||||
| 	// Missing fields will have nil values. | 	// Missing fields will have nil values. | ||||||
| 	vals := make([]core.Value, len(cmd.Fields)) | 	vals := make([]core.Value, len(cmd.fields)) | ||||||
| 	for i, field := range cmd.Fields { | 	for i, field := range cmd.fields { | ||||||
| 		vals[i] = items[field] | 		vals[i] = items[field] | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,36 +10,31 @@ import ( | |||||||
|  |  | ||||||
| func TestHMGetParse(t *testing.T) { | func TestHMGetParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name   string | 		cmd    string | ||||||
| 		args   [][]byte |  | ||||||
| 		key    string | 		key    string | ||||||
| 		fields []string | 		fields []string | ||||||
| 		err    error | 		err    error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hmget", | 			cmd:    "hmget", | ||||||
| 			args:   command.BuildArgs("hmget"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			fields: nil, | 			fields: nil, | ||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hmget person", | 			cmd:    "hmget person", | ||||||
| 			args:   command.BuildArgs("hmget", "person"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			fields: nil, | 			fields: nil, | ||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hmget person name", | 			cmd:    "hmget person name", | ||||||
| 			args:   command.BuildArgs("hmget", "person", "name"), |  | ||||||
| 			key:    "person", | 			key:    "person", | ||||||
| 			fields: []string{"name"}, | 			fields: []string{"name"}, | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hmget person name age", | 			cmd:    "hmget person name age", | ||||||
| 			args:   command.BuildArgs("hmget", "person", "name", "age"), |  | ||||||
| 			key:    "person", | 			key:    "person", | ||||||
| 			fields: []string{"name", "age"}, | 			fields: []string{"name", "age"}, | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| @@ -49,13 +42,12 @@ func TestHMGetParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHMGet, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HMGet) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.key) | 				testx.AssertEqual(t, cmd.fields, test.fields) | ||||||
| 				testx.AssertEqual(t, cm.Fields, test.fields) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -69,7 +61,7 @@ func TestHMGetExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HMGet]("hmget person name") | 		cmd := redis.MustParse(ParseHMGet, "hmget person name") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -87,7 +79,7 @@ func TestHMGetExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
| 		_, _ = db.Hash().Set("person", "happy", true) | 		_, _ = db.Hash().Set("person", "happy", true) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HMGet]("hmget person name happy city") | 		cmd := redis.MustParse(ParseHMGet, "hmget person name happy city") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -106,7 +98,7 @@ func TestHMGetExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HMGet]("hmget person name age") | 		cmd := redis.MustParse(ParseHMGet, "hmget person name age") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,15 +10,15 @@ import ( | |||||||
| // https://redis.io/commands/hmset | // https://redis.io/commands/hmset | ||||||
| type HMSet struct { | type HMSet struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Items map[string]any | 	items map[string]any | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHMSet(b redis.BaseCmd) (*HMSet, error) { | func ParseHMSet(b redis.BaseCmd) (*HMSet, error) { | ||||||
| 	cmd := &HMSet{BaseCmd: b} | 	cmd := &HMSet{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.AnyMap(&cmd.Items), | 		parser.AnyMap(&cmd.items), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -27,7 +27,7 @@ func ParseHMSet(b redis.BaseCmd) (*HMSet, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HMSet) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HMSet) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	count, err := red.Hash().SetMany(cmd.Key, cmd.Items) | 	count, err := red.Hash().SetMany(cmd.key, cmd.items) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,55 +1,46 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestHMSetParse(t *testing.T) { | func TestHMSetParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want HMSet | ||||||
| 		want hash.HMSet |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "hmset", | 			cmd:  "hmset", | ||||||
| 			args: command.BuildArgs("hmset"), | 			want: HMSet{}, | ||||||
| 			want: hash.HMSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hmset person", | 			cmd:  "hmset person", | ||||||
| 			args: command.BuildArgs("hmset", "person"), | 			want: HMSet{}, | ||||||
| 			want: hash.HMSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hmset person name", | 			cmd:  "hmset person name", | ||||||
| 			args: command.BuildArgs("hmset", "person", "name"), | 			want: HMSet{}, | ||||||
| 			want: hash.HMSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hmset person name alice", | 			cmd:  "hmset person name alice", | ||||||
| 			args: command.BuildArgs("hmset", "person", "name", "alice"), | 			want: HMSet{key: "person", items: map[string]any{"name": []byte("alice")}}, | ||||||
| 			want: hash.HMSet{Key: "person", Items: map[string]any{"name": []byte("alice")}}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hmset person name alice age", | 			cmd:  "hmset person name alice age", | ||||||
| 			args: command.BuildArgs("hmset", "person", "name", "alice", "age"), | 			want: HMSet{}, | ||||||
| 			want: hash.HMSet{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hmset person name alice age 25", | 			cmd: "hmset person name alice age 25", | ||||||
| 			args: command.BuildArgs("hmset", "person", "name", "alice", "age", "25"), | 			want: HMSet{key: "person", items: map[string]any{ | ||||||
| 			want: hash.HMSet{Key: "person", Items: map[string]any{ |  | ||||||
| 				"name": []byte("alice"), | 				"name": []byte("alice"), | ||||||
| 				"age":  []byte("25"), | 				"age":  []byte("25"), | ||||||
| 			}}, | 			}}, | ||||||
| @@ -58,13 +49,12 @@ func TestHMSetParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHMSet, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HMSet) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.items, test.want.items) | ||||||
| 				testx.AssertEqual(t, cm.Items, test.want.Items) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -75,7 +65,7 @@ func TestHMSetExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HMSet]("hmset person name alice") | 		cmd := redis.MustParse(ParseHMSet, "hmset person name alice") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -90,7 +80,7 @@ func TestHMSetExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HMSet]("hmset person name alice age 25") | 		cmd := redis.MustParse(ParseHMSet, "hmset person name alice age 25") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -109,7 +99,7 @@ func TestHMSetExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HMSet]("hmset person name bob age 50") | 		cmd := redis.MustParse(ParseHMSet, "hmset person name bob age 50") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -129,7 +119,7 @@ func TestHMSetExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HMSet]("hmset person name bob age 50") | 		cmd := redis.MustParse(ParseHMSet, "hmset person name bob age 50") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -10,35 +10,35 @@ import ( | |||||||
| // https://redis.io/commands/hscan | // https://redis.io/commands/hscan | ||||||
| type HScan struct { | type HScan struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key    string | 	key    string | ||||||
| 	Cursor int | 	cursor int | ||||||
| 	Match  string | 	match  string | ||||||
| 	Count  int | 	count  int | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHScan(b redis.BaseCmd) (*HScan, error) { | func ParseHScan(b redis.BaseCmd) (*HScan, error) { | ||||||
| 	cmd := &HScan{BaseCmd: b} | 	cmd := &HScan{BaseCmd: b} | ||||||
|  |  | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Int(&cmd.Cursor), | 		parser.Int(&cmd.cursor), | ||||||
| 		parser.Named("match", parser.String(&cmd.Match)), | 		parser.Named("match", parser.String(&cmd.match)), | ||||||
| 		parser.Named("count", parser.Int(&cmd.Count)), | 		parser.Named("count", parser.Int(&cmd.count)), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return cmd, err | 		return cmd, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// all keys by default | 	// all keys by default | ||||||
| 	if cmd.Match == "" { | 	if cmd.match == "" { | ||||||
| 		cmd.Match = "*" | 		cmd.match = "*" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HScan) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HScan) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	res, err := red.Hash().Scan(cmd.Key, cmd.Cursor, cmd.Match, cmd.Count) | 	res, err := red.Hash().Scan(cmd.key, cmd.cursor, cmd.match, cmd.count) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/rhash" | 	"github.com/nalgeon/redka/internal/rhash" | ||||||
| @@ -13,8 +11,7 @@ import ( | |||||||
|  |  | ||||||
| func TestHScanParse(t *testing.T) { | func TestHScanParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name   string | 		cmd    string | ||||||
| 		args   [][]byte |  | ||||||
| 		key    string | 		key    string | ||||||
| 		cursor int | 		cursor int | ||||||
| 		match  string | 		match  string | ||||||
| @@ -22,8 +19,7 @@ func TestHScanParse(t *testing.T) { | |||||||
| 		err    error | 		err    error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hscan", | 			cmd:    "hscan", | ||||||
| 			args:   command.BuildArgs("hscan"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			cursor: 0, | 			cursor: 0, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| @@ -31,8 +27,7 @@ func TestHScanParse(t *testing.T) { | |||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hscan person", | 			cmd:    "hscan person", | ||||||
| 			args:   command.BuildArgs("hscan", "person"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			cursor: 0, | 			cursor: 0, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| @@ -40,8 +35,7 @@ func TestHScanParse(t *testing.T) { | |||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hscan person 15", | 			cmd:    "hscan person 15", | ||||||
| 			args:   command.BuildArgs("hscan", "person", "15"), |  | ||||||
| 			key:    "person", | 			key:    "person", | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| @@ -49,8 +43,7 @@ func TestHScanParse(t *testing.T) { | |||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hscan person 15 match *", | 			cmd:    "hscan person 15 match *", | ||||||
| 			args:   command.BuildArgs("hscan", "person", "15", "match", "*"), |  | ||||||
| 			key:    "person", | 			key:    "person", | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| @@ -58,8 +51,7 @@ func TestHScanParse(t *testing.T) { | |||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hscan person 15 match * count 5", | 			cmd:    "hscan person 15 match * count 5", | ||||||
| 			args:   command.BuildArgs("hscan", "person", "15", "match", "*", "count", "5"), |  | ||||||
| 			key:    "person", | 			key:    "person", | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| @@ -67,8 +59,7 @@ func TestHScanParse(t *testing.T) { | |||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hscan person 15 count 5 match *", | 			cmd:    "hscan person 15 count 5 match *", | ||||||
| 			args:   command.BuildArgs("hscan", "person", "15", "count", "5", "match", "*"), |  | ||||||
| 			key:    "person", | 			key:    "person", | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| @@ -76,8 +67,7 @@ func TestHScanParse(t *testing.T) { | |||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hscan person 15 match k2* count 5", | 			cmd:    "hscan person 15 match k2* count 5", | ||||||
| 			args:   command.BuildArgs("hscan", "person", "15", "match", "k2*", "count", "5"), |  | ||||||
| 			key:    "person", | 			key:    "person", | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "k2*", | 			match:  "k2*", | ||||||
| @@ -85,8 +75,7 @@ func TestHScanParse(t *testing.T) { | |||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hscan person ten", | 			cmd:    "hscan person ten", | ||||||
| 			args:   command.BuildArgs("hscan", "person", "ten"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			cursor: 0, | 			cursor: 0, | ||||||
| 			match:  "", | 			match:  "", | ||||||
| @@ -94,8 +83,7 @@ func TestHScanParse(t *testing.T) { | |||||||
| 			err:    redis.ErrInvalidInt, | 			err:    redis.ErrInvalidInt, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hscan person 15 *", | 			cmd:    "hscan person 15 *", | ||||||
| 			args:   command.BuildArgs("hscan", "person", "15", "*"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			cursor: 0, | 			cursor: 0, | ||||||
| 			match:  "", | 			match:  "", | ||||||
| @@ -103,8 +91,7 @@ func TestHScanParse(t *testing.T) { | |||||||
| 			err:    redis.ErrSyntaxError, | 			err:    redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "hscan person 15 * 5", | 			cmd:    "hscan person 15 * 5", | ||||||
| 			args:   command.BuildArgs("hscan", "person", "15", "*", "5"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			cursor: 0, | 			cursor: 0, | ||||||
| 			match:  "", | 			match:  "", | ||||||
| @@ -114,15 +101,14 @@ func TestHScanParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHScan, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				scmd := cmd.(*hash.HScan) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, scmd.Key, test.key) | 				testx.AssertEqual(t, cmd.cursor, test.cursor) | ||||||
| 				testx.AssertEqual(t, scmd.Cursor, test.cursor) | 				testx.AssertEqual(t, cmd.match, test.match) | ||||||
| 				testx.AssertEqual(t, scmd.Match, test.match) | 				testx.AssertEqual(t, cmd.count, test.count) | ||||||
| 				testx.AssertEqual(t, scmd.Count, test.count) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -140,7 +126,7 @@ func TestHScanExec(t *testing.T) { | |||||||
|  |  | ||||||
| 	t.Run("hscan all", func(t *testing.T) { | 	t.Run("hscan all", func(t *testing.T) { | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*hash.HScan]("hscan key 0") | 			cmd := redis.MustParse(ParseHScan, "hscan key 0") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| @@ -156,7 +142,7 @@ func TestHScanExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "2,5,10,f11,11,f12,12,f21,21,f22,22,f31,31") | 			testx.AssertEqual(t, conn.Out(), "2,5,10,f11,11,f12,12,f21,21,f22,22,f31,31") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*hash.HScan]("hscan key 5") | 			cmd := redis.MustParse(ParseHScan, "hscan key 5") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| @@ -170,7 +156,7 @@ func TestHScanExec(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("hscan pattern", func(t *testing.T) { | 	t.Run("hscan pattern", func(t *testing.T) { | ||||||
| 		cmd := command.MustParse[*hash.HScan]("hscan key 0 match f2*") | 		cmd := redis.MustParse(ParseHScan, "hscan key 0 match f2*") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| @@ -189,7 +175,7 @@ func TestHScanExec(t *testing.T) { | |||||||
| 	t.Run("hscan count", func(t *testing.T) { | 	t.Run("hscan count", func(t *testing.T) { | ||||||
| 		{ | 		{ | ||||||
| 			// page 1 | 			// page 1 | ||||||
| 			cmd := command.MustParse[*hash.HScan]("hscan key 0 match * count 2") | 			cmd := redis.MustParse(ParseHScan, "hscan key 0 match * count 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| @@ -206,7 +192,7 @@ func TestHScanExec(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			// page 2 | 			// page 2 | ||||||
| 			cmd := command.MustParse[*hash.HScan]("hscan key 2 match * count 2") | 			cmd := redis.MustParse(ParseHScan, "hscan key 2 match * count 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| @@ -223,7 +209,7 @@ func TestHScanExec(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			// page 3 | 			// page 3 | ||||||
| 			cmd := command.MustParse[*hash.HScan]("hscan key 4 match * count 2") | 			cmd := redis.MustParse(ParseHScan, "hscan key 4 match * count 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| @@ -238,7 +224,7 @@ func TestHScanExec(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			// no more pages | 			// no more pages | ||||||
| 			cmd := command.MustParse[*hash.HScan]("hscan key 5 match * count 2") | 			cmd := redis.MustParse(ParseHScan, "hscan key 5 match * count 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
|   | |||||||
| @@ -10,15 +10,15 @@ import ( | |||||||
| // https://redis.io/commands/hset | // https://redis.io/commands/hset | ||||||
| type HSet struct { | type HSet struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Items map[string]any | 	items map[string]any | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHSet(b redis.BaseCmd) (*HSet, error) { | func ParseHSet(b redis.BaseCmd) (*HSet, error) { | ||||||
| 	cmd := &HSet{BaseCmd: b} | 	cmd := &HSet{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.AnyMap(&cmd.Items), | 		parser.AnyMap(&cmd.items), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -27,7 +27,7 @@ func ParseHSet(b redis.BaseCmd) (*HSet, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HSet) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HSet) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	count, err := red.Hash().SetMany(cmd.Key, cmd.Items) | 	count, err := red.Hash().SetMany(cmd.key, cmd.items) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,55 +1,46 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestHSetParse(t *testing.T) { | func TestHSetParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want HSet | ||||||
| 		want hash.HSet |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "hset", | 			cmd:  "hset", | ||||||
| 			args: command.BuildArgs("hset"), | 			want: HSet{}, | ||||||
| 			want: hash.HSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hset person", | 			cmd:  "hset person", | ||||||
| 			args: command.BuildArgs("hset", "person"), | 			want: HSet{}, | ||||||
| 			want: hash.HSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hset person name", | 			cmd:  "hset person name", | ||||||
| 			args: command.BuildArgs("hset", "person", "name"), | 			want: HSet{}, | ||||||
| 			want: hash.HSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hset person name alice", | 			cmd:  "hset person name alice", | ||||||
| 			args: command.BuildArgs("hset", "person", "name", "alice"), | 			want: HSet{key: "person", items: map[string]any{"name": []byte("alice")}}, | ||||||
| 			want: hash.HSet{Key: "person", Items: map[string]any{"name": []byte("alice")}}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hset person name alice age", | 			cmd:  "hset person name alice age", | ||||||
| 			args: command.BuildArgs("hset", "person", "name", "alice", "age"), | 			want: HSet{}, | ||||||
| 			want: hash.HSet{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hset person name alice age 25", | 			cmd: "hset person name alice age 25", | ||||||
| 			args: command.BuildArgs("hset", "person", "name", "alice", "age", "25"), | 			want: HSet{key: "person", items: map[string]any{ | ||||||
| 			want: hash.HSet{Key: "person", Items: map[string]any{ |  | ||||||
| 				"name": []byte("alice"), | 				"name": []byte("alice"), | ||||||
| 				"age":  []byte("25"), | 				"age":  []byte("25"), | ||||||
| 			}}, | 			}}, | ||||||
| @@ -58,13 +49,12 @@ func TestHSetParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHSet, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HSet) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.items, test.want.items) | ||||||
| 				testx.AssertEqual(t, cm.Items, test.want.Items) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -75,7 +65,7 @@ func TestHSetExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HSet]("hset person name alice") | 		cmd := redis.MustParse(ParseHSet, "hset person name alice") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -90,7 +80,7 @@ func TestHSetExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HSet]("hset person name alice age 25") | 		cmd := redis.MustParse(ParseHSet, "hset person name alice age 25") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -109,7 +99,7 @@ func TestHSetExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HSet]("hset person name bob age 50") | 		cmd := redis.MustParse(ParseHSet, "hset person name bob age 50") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -129,7 +119,7 @@ func TestHSetExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HSet]("hset person name bob age 50") | 		cmd := redis.MustParse(ParseHSet, "hset person name bob age 50") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -7,9 +7,9 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/hsetnx | // https://redis.io/commands/hsetnx | ||||||
| type HSetNX struct { | type HSetNX struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Field string | 	field string | ||||||
| 	Value []byte | 	value []byte | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHSetNX(b redis.BaseCmd) (*HSetNX, error) { | func ParseHSetNX(b redis.BaseCmd) (*HSetNX, error) { | ||||||
| @@ -17,14 +17,14 @@ func ParseHSetNX(b redis.BaseCmd) (*HSetNX, error) { | |||||||
| 	if len(cmd.Args()) != 3 { | 	if len(cmd.Args()) != 3 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	cmd.Field = string(cmd.Args()[1]) | 	cmd.field = string(cmd.Args()[1]) | ||||||
| 	cmd.Value = cmd.Args()[2] | 	cmd.value = cmd.Args()[2] | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HSetNX) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HSetNX) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	ok, err := red.Hash().SetNotExists(cmd.Key, cmd.Field, cmd.Value) | 	ok, err := red.Hash().SetNotExists(cmd.key, cmd.field, cmd.value) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,61 +1,52 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestHSetNXParse(t *testing.T) { | func TestHSetNXParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want HSetNX | ||||||
| 		want hash.HSetNX |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "hsetnx", | 			cmd:  "hsetnx", | ||||||
| 			args: command.BuildArgs("hsetnx"), | 			want: HSetNX{}, | ||||||
| 			want: hash.HSetNX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hsetnx person", | 			cmd:  "hsetnx person", | ||||||
| 			args: command.BuildArgs("hsetnx", "person"), | 			want: HSetNX{}, | ||||||
| 			want: hash.HSetNX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hsetnx person name", | 			cmd:  "hsetnx person name", | ||||||
| 			args: command.BuildArgs("hsetnx", "person", "name"), | 			want: HSetNX{}, | ||||||
| 			want: hash.HSetNX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hsetnx person name alice", | 			cmd:  "hsetnx person name alice", | ||||||
| 			args: command.BuildArgs("hsetnx", "person", "name", "alice"), | 			want: HSetNX{key: "person", field: "name", value: []byte("alice")}, | ||||||
| 			want: hash.HSetNX{Key: "person", Field: "name", Value: []byte("alice")}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hsetnx person name alice age 25", | 			cmd:  "hsetnx person name alice age 25", | ||||||
| 			args: command.BuildArgs("hsetnx", "person", "name", "alice", "age", "25"), | 			want: HSetNX{}, | ||||||
| 			want: hash.HSetNX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHSetNX, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HSetNX) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.value, test.want.value) | ||||||
| 				testx.AssertEqual(t, cm.Value, test.want.Value) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -66,7 +57,7 @@ func TestHSetNXExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HSetNX]("hsetnx person name alice") | 		cmd := redis.MustParse(ParseHSetNX, "hsetnx person name alice") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -83,7 +74,7 @@ func TestHSetNXExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HSetNX]("hsetnx person name bob") | 		cmd := redis.MustParse(ParseHSetNX, "hsetnx person name bob") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/hvals | // https://redis.io/commands/hvals | ||||||
| type HVals struct { | type HVals struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key string | 	key string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseHVals(b redis.BaseCmd) (*HVals, error) { | func ParseHVals(b redis.BaseCmd) (*HVals, error) { | ||||||
| @@ -15,12 +15,12 @@ func ParseHVals(b redis.BaseCmd) (*HVals, error) { | |||||||
| 	if len(cmd.Args()) != 1 { | 	if len(cmd.Args()) != 1 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *HVals) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *HVals) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	vals, err := red.Hash().Values(cmd.Key) | 	vals, err := red.Hash().Values(cmd.key) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package hash_test | package hash | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/hash" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,38 +10,33 @@ import ( | |||||||
|  |  | ||||||
| func TestHValsParse(t *testing.T) { | func TestHValsParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		args [][]byte | 		key string | ||||||
| 		key  string | 		err error | ||||||
| 		err  error |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "hvals", | 			cmd: "hvals", | ||||||
| 			args: command.BuildArgs("hvals"), | 			key: "", | ||||||
| 			key:  "", | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hvals person", | 			cmd: "hvals person", | ||||||
| 			args: command.BuildArgs("hvals", "person"), | 			key: "person", | ||||||
| 			key:  "person", | 			err: nil, | ||||||
| 			err:  nil, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "hvals person name", | 			cmd: "hvals person name", | ||||||
| 			args: command.BuildArgs("hvals", "person", "name"), | 			key: "", | ||||||
| 			key:  "", | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseHVals, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*hash.HVals) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.key) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -57,7 +50,7 @@ func TestHValsExec(t *testing.T) { | |||||||
| 		_, _ = db.Hash().Set("person", "name", "alice") | 		_, _ = db.Hash().Set("person", "name", "alice") | ||||||
| 		_, _ = db.Hash().Set("person", "age", 25) | 		_, _ = db.Hash().Set("person", "age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HVals]("hvals person") | 		cmd := redis.MustParse(ParseHVals, "hvals person") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
| @@ -69,7 +62,7 @@ func TestHValsExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*hash.HVals]("hvals person") | 		cmd := redis.MustParse(ParseHVals, "hvals person") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,13 +10,13 @@ import ( | |||||||
| // https://redis.io/commands/del | // https://redis.io/commands/del | ||||||
| type Del struct { | type Del struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Keys []string | 	keys []string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseDel(b redis.BaseCmd) (*Del, error) { | func ParseDel(b redis.BaseCmd) (*Del, error) { | ||||||
| 	cmd := &Del{BaseCmd: b} | 	cmd := &Del{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.Strings(&cmd.Keys), | 		parser.Strings(&cmd.keys), | ||||||
| 	).Required(1).Run(cmd.Args()) | 	).Required(1).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return cmd, err | 		return cmd, err | ||||||
| @@ -25,7 +25,7 @@ func ParseDel(b redis.BaseCmd) (*Del, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *Del) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *Del) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	count, err := red.Key().Delete(cmd.Keys...) | 	count, err := red.Key().Delete(cmd.keys...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,47 +1,41 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestDelParse(t *testing.T) { | func TestDelParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte |  | ||||||
| 		want []string | 		want []string | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "del", | 			cmd:  "del", | ||||||
| 			args: command.BuildArgs("del"), |  | ||||||
| 			want: nil, | 			want: nil, | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "del name", | 			cmd:  "del name", | ||||||
| 			args: command.BuildArgs("del", "name"), |  | ||||||
| 			want: []string{"name"}, | 			want: []string{"name"}, | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "del name age", | 			cmd:  "del name age", | ||||||
| 			args: command.BuildArgs("del", "name", "age"), |  | ||||||
| 			want: []string{"name", "age"}, | 			want: []string{"name", "age"}, | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseDel, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*key.Del).Keys, test.want) | 				testx.AssertEqual(t, cmd.keys, test.want) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -49,33 +43,29 @@ func TestDelParse(t *testing.T) { | |||||||
|  |  | ||||||
| func TestDelExec(t *testing.T) { | func TestDelExec(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		cmd  *key.Del | 		res any | ||||||
| 		res  any | 		out string | ||||||
| 		out  string |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "del one", | 			cmd: "del name", | ||||||
| 			cmd:  command.MustParse[*key.Del]("del name"), | 			res: 1, | ||||||
| 			res:  1, | 			out: "1", | ||||||
| 			out:  "1", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "del all", | 			cmd: "del name age", | ||||||
| 			cmd:  command.MustParse[*key.Del]("del name age"), | 			res: 2, | ||||||
| 			res:  2, | 			out: "2", | ||||||
| 			out:  "2", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "del some", | 			cmd: "del name age street", | ||||||
| 			cmd:  command.MustParse[*key.Del]("del name age street"), | 			res: 2, | ||||||
| 			res:  2, | 			out: "2", | ||||||
| 			out:  "2", |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			db, red := getDB(t) | 			db, red := getDB(t) | ||||||
| 			defer db.Close() | 			defer db.Close() | ||||||
|  |  | ||||||
| @@ -84,7 +74,8 @@ func TestDelExec(t *testing.T) { | |||||||
| 			_ = db.Str().Set("city", "paris") | 			_ = db.Str().Set("city", "paris") | ||||||
|  |  | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := test.cmd.Run(conn, red) | 			cmd := redis.MustParse(ParseDel, test.cmd) | ||||||
|  | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| 			testx.AssertEqual(t, res, test.res) | 			testx.AssertEqual(t, res, test.res) | ||||||
| 			testx.AssertEqual(t, conn.Out(), test.out) | 			testx.AssertEqual(t, conn.Out(), test.out) | ||||||
|   | |||||||
| @@ -10,13 +10,13 @@ import ( | |||||||
| // https://redis.io/commands/exists | // https://redis.io/commands/exists | ||||||
| type Exists struct { | type Exists struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Keys []string | 	keys []string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseExists(b redis.BaseCmd) (*Exists, error) { | func ParseExists(b redis.BaseCmd) (*Exists, error) { | ||||||
| 	cmd := &Exists{BaseCmd: b} | 	cmd := &Exists{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.Strings(&cmd.Keys), | 		parser.Strings(&cmd.keys), | ||||||
| 	).Required(1).Run(cmd.Args()) | 	).Required(1).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return cmd, err | 		return cmd, err | ||||||
| @@ -25,7 +25,7 @@ func ParseExists(b redis.BaseCmd) (*Exists, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *Exists) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *Exists) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	count, err := red.Key().Count(cmd.Keys...) | 	count, err := red.Key().Count(cmd.keys...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,47 +1,41 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestExistsParse(t *testing.T) { | func TestExistsParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte |  | ||||||
| 		want []string | 		want []string | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "exists", | 			cmd:  "exists", | ||||||
| 			args: command.BuildArgs("exists"), |  | ||||||
| 			want: nil, | 			want: nil, | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "exists name", | 			cmd:  "exists name", | ||||||
| 			args: command.BuildArgs("exists", "name"), |  | ||||||
| 			want: []string{"name"}, | 			want: []string{"name"}, | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "exists name age", | 			cmd:  "exists name age", | ||||||
| 			args: command.BuildArgs("exists", "name", "age"), |  | ||||||
| 			want: []string{"name", "age"}, | 			want: []string{"name", "age"}, | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseExists, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*key.Exists).Keys, test.want) | 				testx.AssertEqual(t, cmd.keys, test.want) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -56,35 +50,32 @@ func TestExistsExec(t *testing.T) { | |||||||
| 	_ = db.Str().Set("city", "paris") | 	_ = db.Str().Set("city", "paris") | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		cmd  *key.Exists | 		res any | ||||||
| 		res  any | 		out string | ||||||
| 		out  string |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "exists one", | 			cmd: "exists name", | ||||||
| 			cmd:  command.MustParse[*key.Exists]("exists name"), | 			res: 1, | ||||||
| 			res:  1, | 			out: "1", | ||||||
| 			out:  "1", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "exists all", | 			cmd: "exists name age", | ||||||
| 			cmd:  command.MustParse[*key.Exists]("exists name age"), | 			res: 2, | ||||||
| 			res:  2, | 			out: "2", | ||||||
| 			out:  "2", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "exists some", | 			cmd: "exists name age street", | ||||||
| 			cmd:  command.MustParse[*key.Exists]("exists name age street"), | 			res: 2, | ||||||
| 			res:  2, | 			out: "2", | ||||||
| 			out:  "2", |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := test.cmd.Run(conn, red) | 			cmd := redis.MustParse(ParseExists, test.cmd) | ||||||
|  | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| 			testx.AssertEqual(t, res, test.res) | 			testx.AssertEqual(t, res, test.res) | ||||||
| 			testx.AssertEqual(t, conn.Out(), test.out) | 			testx.AssertEqual(t, conn.Out(), test.out) | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ import ( | |||||||
| // https://redis.io/commands/expire | // https://redis.io/commands/expire | ||||||
| type Expire struct { | type Expire struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key string | 	key string | ||||||
| 	TTL time.Duration | 	ttl time.Duration | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseExpire(b redis.BaseCmd, multi int) (*Expire, error) { | func ParseExpire(b redis.BaseCmd, multi int) (*Expire, error) { | ||||||
| @@ -22,19 +22,19 @@ func ParseExpire(b redis.BaseCmd, multi int) (*Expire, error) { | |||||||
|  |  | ||||||
| 	var ttl int | 	var ttl int | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Int(&ttl), | 		parser.Int(&ttl), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return cmd, err | 		return cmd, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cmd.TTL = time.Duration(multi*ttl) * time.Millisecond | 	cmd.ttl = time.Duration(multi*ttl) * time.Millisecond | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *Expire) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *Expire) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	err := red.Key().Expire(cmd.Key, cmd.TTL) | 	err := red.Key().Expire(cmd.key, cmd.ttl) | ||||||
| 	if err != nil && err != core.ErrNotFound { | 	if err != nil && err != core.ErrNotFound { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,80 +1,79 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestExpireParse(t *testing.T) { | func TestExpireParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		args [][]byte | 		key string | ||||||
| 		key  string | 		ttl time.Duration | ||||||
| 		ttl  time.Duration | 		err error | ||||||
| 		err  error |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "expire", | 			cmd: "expire", | ||||||
| 			args: command.BuildArgs("expire"), | 			key: "", | ||||||
| 			key:  "", | 			ttl: 0, | ||||||
| 			ttl:  0, | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "expire name", | 			cmd: "expire name", | ||||||
| 			args: command.BuildArgs("expire", "name"), | 			key: "", | ||||||
| 			key:  "", | 			ttl: 0, | ||||||
| 			ttl:  0, | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "expire name 60", | 			cmd: "expire name 60", | ||||||
| 			args: command.BuildArgs("expire", "name", "60"), | 			key: "name", | ||||||
| 			key:  "name", | 			ttl: 60 * 1000 * time.Millisecond, | ||||||
| 			ttl:  60 * 1000 * time.Millisecond, | 			err: nil, | ||||||
| 			err:  nil, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "expire name age", | 			cmd: "expire name age", | ||||||
| 			args: command.BuildArgs("expire", "name", "age"), | 			key: "", | ||||||
| 			key:  "", | 			ttl: 0, | ||||||
| 			ttl:  0, | 			err: redis.ErrInvalidInt, | ||||||
| 			err:  redis.ErrInvalidInt, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "expire name 60 age 60", | 			cmd: "expire name 60 age 60", | ||||||
| 			args: command.BuildArgs("expire", "name", "60", "age", "60"), | 			key: "", | ||||||
| 			key:  "", | 			ttl: 0, | ||||||
| 			ttl:  0, | 			err: redis.ErrSyntaxError, | ||||||
| 			err:  redis.ErrSyntaxError, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	parse := func(b redis.BaseCmd) (*Expire, error) { | ||||||
|  | 		return ParseExpire(b, 1000) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(parse, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*key.Expire).Key, test.key) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cmd.(*key.Expire).TTL, test.ttl) | 				testx.AssertEqual(t, cmd.ttl, test.ttl) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestExpireExec(t *testing.T) { | func TestExpireExec(t *testing.T) { | ||||||
|  | 	parse := func(b redis.BaseCmd) (*Expire, error) { | ||||||
|  | 		return ParseExpire(b, 1000) | ||||||
|  | 	} | ||||||
| 	t.Run("create expire", func(t *testing.T) { | 	t.Run("create expire", func(t *testing.T) { | ||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Expire]("expire name 60") | 		cmd := redis.MustParse(parse, "expire name 60") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -92,7 +91,7 @@ func TestExpireExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().SetExpires("name", "alice", 60*time.Second) | 		_ = db.Str().SetExpires("name", "alice", 60*time.Second) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Expire]("expire name 30") | 		cmd := redis.MustParse(parse, "expire name 30") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -110,7 +109,7 @@ func TestExpireExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Expire]("expire name 0") | 		cmd := redis.MustParse(parse, "expire name 0") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -127,7 +126,7 @@ func TestExpireExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Expire]("expire name -10") | 		cmd := redis.MustParse(parse, "expire name -10") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -144,7 +143,7 @@ func TestExpireExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Expire]("expire age 60") | 		cmd := redis.MustParse(parse, "expire age 60") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ import ( | |||||||
| // https://redis.io/commands/expireat | // https://redis.io/commands/expireat | ||||||
| type ExpireAt struct { | type ExpireAt struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key string | 	key string | ||||||
| 	At  time.Time | 	at  time.Time | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseExpireAt(b redis.BaseCmd, multi int) (*ExpireAt, error) { | func ParseExpireAt(b redis.BaseCmd, multi int) (*ExpireAt, error) { | ||||||
| @@ -22,19 +22,19 @@ func ParseExpireAt(b redis.BaseCmd, multi int) (*ExpireAt, error) { | |||||||
|  |  | ||||||
| 	var at int | 	var at int | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Int(&at), | 		parser.Int(&at), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return cmd, err | 		return cmd, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cmd.At = time.UnixMilli(int64(multi * at)) | 	cmd.at = time.UnixMilli(int64(multi * at)) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ExpireAt) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ExpireAt) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	err := red.Key().ExpireAt(cmd.Key, cmd.At) | 	err := red.Key().ExpireAt(cmd.key, cmd.at) | ||||||
| 	if err != nil && err != core.ErrNotFound { | 	if err != nil && err != core.ErrNotFound { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,74 +1,73 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestExpireAtParse(t *testing.T) { | func TestExpireAtParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		args [][]byte | 		key string | ||||||
| 		key  string | 		at  time.Time | ||||||
| 		at   time.Time | 		err error | ||||||
| 		err  error |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "expireat", | 			cmd: "expireat", | ||||||
| 			args: command.BuildArgs("expireat"), | 			key: "", | ||||||
| 			key:  "", | 			at:  time.Time{}, | ||||||
| 			at:   time.Time{}, | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "expireat name", | 			cmd: "expireat name", | ||||||
| 			args: command.BuildArgs("expire", "name"), | 			key: "", | ||||||
| 			key:  "", | 			at:  time.Time{}, | ||||||
| 			at:   time.Time{}, | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "expireat name 60", | 			cmd: "expireat name 60", | ||||||
| 			args: command.BuildArgs("expireat", "name", fmt.Sprintf("%d", time.Now().Add(60*time.Second).Unix())), | 			key: "name", | ||||||
| 			key:  "name", | 			at:  time.UnixMilli(60 * 1000), | ||||||
| 			at:   time.Now().Add(60 * time.Second), | 			err: nil, | ||||||
| 			err:  nil, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "expireat name age", | 			cmd: "expireat name age", | ||||||
| 			args: command.BuildArgs("expireat", "name", "age"), | 			key: "", | ||||||
| 			key:  "", | 			at:  time.Time{}, | ||||||
| 			at:   time.Time{}, | 			err: redis.ErrInvalidInt, | ||||||
| 			err:  redis.ErrInvalidInt, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "expireat name 60 age 60", | 			cmd: "expireat name 60 age 60", | ||||||
| 			args: command.BuildArgs("expireat", "name", "60", "age", "60"), | 			key: "", | ||||||
| 			key:  "", | 			at:  time.Time{}, | ||||||
| 			at:   time.Time{}, | 			err: redis.ErrSyntaxError, | ||||||
| 			err:  redis.ErrSyntaxError, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	parse := func(b redis.BaseCmd) (*ExpireAt, error) { | ||||||
|  | 		return ParseExpireAt(b, 1000) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(parse, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*key.ExpireAt).Key, test.key) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cmd.(*key.ExpireAt).At.Unix(), test.at.Unix()) | 				testx.AssertEqual(t, cmd.at.Unix(), test.at.Unix()) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestExpireAtExec(t *testing.T) { | func TestExpireAtExec(t *testing.T) { | ||||||
|  | 	parse := func(b redis.BaseCmd) (*ExpireAt, error) { | ||||||
|  | 		return ParseExpireAt(b, 1000) | ||||||
|  | 	} | ||||||
| 	t.Run("create expireat", func(t *testing.T) { | 	t.Run("create expireat", func(t *testing.T) { | ||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| @@ -76,7 +75,7 @@ func TestExpireAtExec(t *testing.T) { | |||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		expireAt := time.Now().Add(60 * time.Second) | 		expireAt := time.Now().Add(60 * time.Second) | ||||||
| 		cmd := command.MustParse[*key.ExpireAt](fmt.Sprintf("expireat name %d", expireAt.Unix())) | 		cmd := redis.MustParse(parse, fmt.Sprintf("expireat name %d", expireAt.Unix())) | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -94,14 +93,14 @@ func TestExpireAtExec(t *testing.T) { | |||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		expireAt := time.Now() | 		expireAt := time.Now() | ||||||
| 		cmd := command.MustParse[*key.ExpireAt](fmt.Sprintf("expireat name %d", expireAt.Add(60*time.Second).Unix())) | 		cmd := redis.MustParse(parse, fmt.Sprintf("expireat name %d", expireAt.Add(60*time.Second).Unix())) | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| 		testx.AssertEqual(t, res, true) | 		testx.AssertEqual(t, res, true) | ||||||
| 		testx.AssertEqual(t, conn.Out(), "1") | 		testx.AssertEqual(t, conn.Out(), "1") | ||||||
|  |  | ||||||
| 		cmd = command.MustParse[*key.ExpireAt](fmt.Sprintf("expireat name %d", expireAt.Add(20*time.Second).Unix())) | 		cmd = redis.MustParse(parse, fmt.Sprintf("expireat name %d", expireAt.Add(20*time.Second).Unix())) | ||||||
| 		conn = redis.NewFakeConn() | 		conn = redis.NewFakeConn() | ||||||
| 		res, err = cmd.Run(conn, red) | 		res, err = cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -118,7 +117,7 @@ func TestExpireAtExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.ExpireAt]("expireat name 0") | 		cmd := redis.MustParse(parse, "expireat name 0") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -135,7 +134,7 @@ func TestExpireAtExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.ExpireAt]("expireat name -10") | 		cmd := redis.MustParse(parse, "expireat name -10") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -152,7 +151,7 @@ func TestExpireAtExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.ExpireAt]("expireat age 1700000000") | 		cmd := redis.MustParse(parse, "expireat age 1700000000") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -1,40 +1,34 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestFlushDBParse(t *testing.T) { | func TestFlushDBParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		args [][]byte | 		err error | ||||||
| 		err  error |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "flushdb", | 			cmd: "flushdb", | ||||||
| 			args: command.BuildArgs("flushdb"), | 			err: nil, | ||||||
| 			err:  nil, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "flushdb name", | 			cmd: "flushdb name", | ||||||
| 			args: command.BuildArgs("flushdb", "name"), | 			err: redis.ErrSyntaxError, | ||||||
| 			err:  redis.ErrSyntaxError, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "flushdb 1", | 			cmd: "flushdb 1", | ||||||
| 			args: command.BuildArgs("flushdb", "1"), | 			err: redis.ErrSyntaxError, | ||||||
| 			err:  redis.ErrSyntaxError, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			_, err := command.Parse(test.args) | 			_, err := redis.Parse(ParseFlushDB, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -48,7 +42,7 @@ func TestFlushDBExec(t *testing.T) { | |||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
| 		_ = db.Str().Set("age", 25) | 		_ = db.Str().Set("age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.FlushDB]("flushdb") | 		cmd := redis.MustParse(ParseFlushDB, "flushdb") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -63,7 +57,7 @@ func TestFlushDBExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.FlushDB]("flushdb") | 		cmd := redis.MustParse(ParseFlushDB, "flushdb") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/keys | // https://redis.io/commands/keys | ||||||
| type Keys struct { | type Keys struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Pattern string | 	pattern string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseKeys(b redis.BaseCmd) (*Keys, error) { | func ParseKeys(b redis.BaseCmd) (*Keys, error) { | ||||||
| @@ -15,12 +15,12 @@ func ParseKeys(b redis.BaseCmd) (*Keys, error) { | |||||||
| 	if len(cmd.Args()) != 1 { | 	if len(cmd.Args()) != 1 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Pattern = string(cmd.Args()[0]) | 	cmd.pattern = string(cmd.Args()[0]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *Keys) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *Keys) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	keys, err := red.Key().Keys(cmd.Pattern) | 	keys, err := red.Key().Keys(cmd.pattern) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,43 +10,38 @@ import ( | |||||||
|  |  | ||||||
| func TestKeysParse(t *testing.T) { | func TestKeysParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte |  | ||||||
| 		want string | 		want string | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "keys", | 			cmd:  "keys", | ||||||
| 			args: command.BuildArgs("keys"), |  | ||||||
| 			want: "", | 			want: "", | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "keys *", | 			cmd:  "keys *", | ||||||
| 			args: command.BuildArgs("keys", "*"), |  | ||||||
| 			want: "*", | 			want: "*", | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "keys 2*", | 			cmd:  "keys k2*", | ||||||
| 			args: command.BuildArgs("keys", "k2*"), |  | ||||||
| 			want: "k2*", | 			want: "k2*", | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "keys * k2*", | 			cmd:  "keys * k2*", | ||||||
| 			args: command.BuildArgs("keys", "*", "k2*"), |  | ||||||
| 			want: "", | 			want: "", | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseKeys, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*key.Keys).Pattern, test.want) | 				testx.AssertEqual(t, cmd.pattern, test.want) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -65,41 +58,37 @@ func TestKeysExec(t *testing.T) { | |||||||
| 	_ = db.Str().Set("k31", "31") | 	_ = db.Str().Set("k31", "31") | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		cmd  *key.Keys | 		res []string | ||||||
| 		res  []string | 		out string | ||||||
| 		out  string |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "all keys", | 			cmd: "keys *", | ||||||
| 			cmd:  command.MustParse[*key.Keys]("keys *"), | 			res: []string{"k11", "k12", "k21", "k22", "k31"}, | ||||||
| 			res:  []string{"k11", "k12", "k21", "k22", "k31"}, | 			out: "5,k11,k12,k21,k22,k31", | ||||||
| 			out:  "5,k11,k12,k21,k22,k31", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "some keys", | 			cmd: "keys k2*", | ||||||
| 			cmd:  command.MustParse[*key.Keys]("keys k2*"), | 			res: []string{"k21", "k22"}, | ||||||
| 			res:  []string{"k21", "k22"}, | 			out: "2,k21,k22", | ||||||
| 			out:  "2,k21,k22", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "one key", | 			cmd: "keys k12", | ||||||
| 			cmd:  command.MustParse[*key.Keys]("keys k12"), | 			res: []string{"k12"}, | ||||||
| 			res:  []string{"k12"}, | 			out: "1,k12", | ||||||
| 			out:  "1,k12", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "not found", | 			cmd: "keys name", | ||||||
| 			cmd:  command.MustParse[*key.Keys]("keys name"), | 			res: []string{}, | ||||||
| 			res:  []string{}, | 			out: "0", | ||||||
| 			out:  "0", |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			keys, err := test.cmd.Run(conn, red) | 			cmd := redis.MustParse(ParseKeys, test.cmd) | ||||||
|  | 			keys, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| 			for i, key := range keys.([]core.Key) { | 			for i, key := range keys.([]core.Key) { | ||||||
| 				testx.AssertEqual(t, key.Key, test.res[i]) | 				testx.AssertEqual(t, key.Key, test.res[i]) | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import ( | |||||||
| // https://redis.io/commands/persist | // https://redis.io/commands/persist | ||||||
| type Persist struct { | type Persist struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key string | 	key string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParsePersist(b redis.BaseCmd) (*Persist, error) { | func ParsePersist(b redis.BaseCmd) (*Persist, error) { | ||||||
| @@ -18,12 +18,12 @@ func ParsePersist(b redis.BaseCmd) (*Persist, error) { | |||||||
| 	if len(cmd.Args()) != 1 { | 	if len(cmd.Args()) != 1 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *Persist) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *Persist) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	err := red.Key().Persist(cmd.Key) | 	err := red.Key().Persist(cmd.key) | ||||||
| 	if err != nil && err != core.ErrNotFound { | 	if err != nil && err != core.ErrNotFound { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,48 +1,42 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestPersistParse(t *testing.T) { | func TestPersistParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		args [][]byte | 		key string | ||||||
| 		key  string | 		err error | ||||||
| 		err  error |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "persist", | 			cmd: "persist", | ||||||
| 			args: command.BuildArgs("persist"), | 			key: "", | ||||||
| 			key:  "", | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "persist name", | 			cmd: "persist name", | ||||||
| 			args: command.BuildArgs("persist", "name"), | 			key: "name", | ||||||
| 			key:  "name", | 			err: nil, | ||||||
| 			err:  nil, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "persist name age", | 			cmd: "persist name age", | ||||||
| 			args: command.BuildArgs("persist", "name", "age"), | 			key: "", | ||||||
| 			key:  "", | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParsePersist, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*key.Persist).Key, test.key) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -55,7 +49,7 @@ func TestPersistExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Persist]("persist name") | 		cmd := redis.MustParse(ParsePersist, "persist name") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -72,7 +66,7 @@ func TestPersistExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().SetExpires("name", "alice", 60*time.Second) | 		_ = db.Str().SetExpires("name", "alice", 60*time.Second) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Persist]("persist name") | 		cmd := redis.MustParse(ParsePersist, "persist name") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -89,7 +83,7 @@ func TestPersistExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Persist]("persist age") | 		cmd := redis.MustParse(ParsePersist, "persist age") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -1,80 +1,79 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestPExpireParse(t *testing.T) { | func TestPExpireParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		args [][]byte | 		key string | ||||||
| 		key  string | 		ttl time.Duration | ||||||
| 		ttl  time.Duration | 		err error | ||||||
| 		err  error |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "pexpire", | 			cmd: "pexpire", | ||||||
| 			args: command.BuildArgs("pexpire"), | 			key: "", | ||||||
| 			key:  "", | 			ttl: 0, | ||||||
| 			ttl:  0, | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "pexpire name", | 			cmd: "pexpire name", | ||||||
| 			args: command.BuildArgs("pexpire", "name"), | 			key: "", | ||||||
| 			key:  "", | 			ttl: 0, | ||||||
| 			ttl:  0, | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "pexpire name 5000", | 			cmd: "pexpire name 5000", | ||||||
| 			args: command.BuildArgs("pexpire", "name", "5000"), | 			key: "name", | ||||||
| 			key:  "name", | 			ttl: 5000 * time.Millisecond, | ||||||
| 			ttl:  5000 * time.Millisecond, | 			err: nil, | ||||||
| 			err:  nil, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "pexpire name age", | 			cmd: "pexpire name age", | ||||||
| 			args: command.BuildArgs("pexpire", "name", "age"), | 			key: "", | ||||||
| 			key:  "", | 			ttl: 0, | ||||||
| 			ttl:  0, | 			err: redis.ErrInvalidInt, | ||||||
| 			err:  redis.ErrInvalidInt, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "pexpire name 100 age 100", | 			cmd: "pexpire name 100 age 100", | ||||||
| 			args: command.BuildArgs("pexpire", "name", "100", "age", "100"), | 			key: "", | ||||||
| 			key:  "", | 			ttl: 0, | ||||||
| 			ttl:  0, | 			err: redis.ErrSyntaxError, | ||||||
| 			err:  redis.ErrSyntaxError, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	parse := func(b redis.BaseCmd) (*Expire, error) { | ||||||
|  | 		return ParseExpire(b, 1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(parse, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*key.Expire).Key, test.key) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cmd.(*key.Expire).TTL, test.ttl) | 				testx.AssertEqual(t, cmd.ttl, test.ttl) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestPExpireExec(t *testing.T) { | func TestPExpireExec(t *testing.T) { | ||||||
| 	db, red := getDB(t) | 	parse := func(b redis.BaseCmd) (*Expire, error) { | ||||||
| 	defer db.Close() | 		return ParseExpire(b, 1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	t.Run("create pexpire", func(t *testing.T) { | 	t.Run("create pexpire", func(t *testing.T) { | ||||||
|  | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Expire]("pexpire name 60000") | 		cmd := redis.MustParse(parse, "pexpire name 60000") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -87,9 +86,11 @@ func TestPExpireExec(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("update pexpire", func(t *testing.T) { | 	t.Run("update pexpire", func(t *testing.T) { | ||||||
|  | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
| 		_ = db.Str().SetExpires("name", "alice", 60*time.Second) | 		_ = db.Str().SetExpires("name", "alice", 60*time.Second) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Expire]("pexpire name 30000") | 		cmd := redis.MustParse(parse, "pexpire name 30000") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -102,9 +103,11 @@ func TestPExpireExec(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("set to zero", func(t *testing.T) { | 	t.Run("set to zero", func(t *testing.T) { | ||||||
|  | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Expire]("pexpire name 0") | 		cmd := redis.MustParse(parse, "pexpire name 0") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -116,9 +119,11 @@ func TestPExpireExec(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("negative", func(t *testing.T) { | 	t.Run("negative", func(t *testing.T) { | ||||||
|  | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Expire]("pexpire name -1000") | 		cmd := redis.MustParse(parse, "pexpire name -1000") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -130,9 +135,11 @@ func TestPExpireExec(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("not found", func(t *testing.T) { | 	t.Run("not found", func(t *testing.T) { | ||||||
|  | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Expire]("pexpire age 1000") | 		cmd := redis.MustParse(parse, "pexpire age 1000") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -1,11 +1,9 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"slices" | 	"slices" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -13,30 +11,26 @@ import ( | |||||||
|  |  | ||||||
| func TestRandomKeyParse(t *testing.T) { | func TestRandomKeyParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		args [][]byte | 		err error | ||||||
| 		err  error |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "randomkey", | 			cmd: "randomkey", | ||||||
| 			args: command.BuildArgs("randomkey"), | 			err: nil, | ||||||
| 			err:  nil, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "randomkey name", | 			cmd: "randomkey name", | ||||||
| 			args: command.BuildArgs("randomkey", "name"), | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "randomkey name age", | 			cmd: "randomkey name age", | ||||||
| 			args: command.BuildArgs("randomkey", "name", "age"), | 			err: redis.ErrInvalidArgNum, | ||||||
| 			err:  redis.ErrInvalidArgNum, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			_, err := command.Parse(test.args) | 			_, err := redis.Parse(ParseRandomKey, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -52,7 +46,7 @@ func TestRandomKeyExec(t *testing.T) { | |||||||
| 		keys := []string{"name", "age", "city"} | 		keys := []string{"name", "age", "city"} | ||||||
|  |  | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		cmd := command.MustParse[*key.RandomKey]("randomkey") | 		cmd := redis.MustParse(ParseRandomKey, "randomkey") | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| 		testx.AssertEqual(t, slices.Contains(keys, res.(core.Key).Key), true) | 		testx.AssertEqual(t, slices.Contains(keys, res.(core.Key).Key), true) | ||||||
| @@ -62,7 +56,7 @@ func TestRandomKeyExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		cmd := command.MustParse[*key.RandomKey]("randomkey") | 		cmd := redis.MustParse(ParseRandomKey, "randomkey") | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| 		testx.AssertEqual(t, res, nil) | 		testx.AssertEqual(t, res, nil) | ||||||
|   | |||||||
| @@ -7,8 +7,8 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/rename | // https://redis.io/commands/rename | ||||||
| type Rename struct { | type Rename struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key    string | 	key    string | ||||||
| 	NewKey string | 	newKey string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseRename(b redis.BaseCmd) (*Rename, error) { | func ParseRename(b redis.BaseCmd) (*Rename, error) { | ||||||
| @@ -16,13 +16,13 @@ func ParseRename(b redis.BaseCmd) (*Rename, error) { | |||||||
| 	if len(cmd.Args()) != 2 { | 	if len(cmd.Args()) != 2 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	cmd.NewKey = string(cmd.Args()[1]) | 	cmd.newKey = string(cmd.Args()[1]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *Rename) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *Rename) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	err := red.Key().Rename(cmd.Key, cmd.NewKey) | 	err := red.Key().Rename(cmd.key, cmd.newKey) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,29 +10,25 @@ import ( | |||||||
|  |  | ||||||
| func TestRenameParse(t *testing.T) { | func TestRenameParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name   string | 		cmd    string | ||||||
| 		args   [][]byte |  | ||||||
| 		key    string | 		key    string | ||||||
| 		newKey string | 		newKey string | ||||||
| 		err    error | 		err    error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name:   "rename", | 			cmd:    "rename", | ||||||
| 			args:   command.BuildArgs("rename"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			newKey: "", | 			newKey: "", | ||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "rename name", | 			cmd:    "rename name", | ||||||
| 			args:   command.BuildArgs("rename", "name"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			newKey: "", | 			newKey: "", | ||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "rename name title", | 			cmd:    "rename name title", | ||||||
| 			args:   command.BuildArgs("rename", "name", "title"), |  | ||||||
| 			key:    "name", | 			key:    "name", | ||||||
| 			newKey: "title", | 			newKey: "title", | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| @@ -42,12 +36,12 @@ func TestRenameParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseRename, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*key.Rename).Key, test.key) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cmd.(*key.Rename).NewKey, test.newKey) | 				testx.AssertEqual(t, cmd.newKey, test.newKey) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -60,7 +54,7 @@ func TestRenameExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Rename]("rename name title") | 		cmd := redis.MustParse(ParseRename, "rename name title") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -80,7 +74,7 @@ func TestRenameExec(t *testing.T) { | |||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
| 		_ = db.Str().Set("title", "bob") | 		_ = db.Str().Set("title", "bob") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Rename]("rename name title") | 		cmd := redis.MustParse(ParseRename, "rename name title") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -101,7 +95,7 @@ func TestRenameExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Rename]("rename name name") | 		cmd := redis.MustParse(ParseRename, "rename name name") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -120,7 +114,7 @@ func TestRenameExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("title", "bob") | 		_ = db.Str().Set("title", "bob") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.Rename]("rename name title") | 		cmd := redis.MustParse(ParseRename, "rename name title") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertEqual(t, err, core.ErrNotFound) | 		testx.AssertEqual(t, err, core.ErrNotFound) | ||||||
|   | |||||||
| @@ -7,8 +7,8 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/renamenx | // https://redis.io/commands/renamenx | ||||||
| type RenameNX struct { | type RenameNX struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key    string | 	key    string | ||||||
| 	NewKey string | 	newKey string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseRenameNX(b redis.BaseCmd) (*RenameNX, error) { | func ParseRenameNX(b redis.BaseCmd) (*RenameNX, error) { | ||||||
| @@ -16,13 +16,13 @@ func ParseRenameNX(b redis.BaseCmd) (*RenameNX, error) { | |||||||
| 	if len(cmd.Args()) != 2 { | 	if len(cmd.Args()) != 2 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	cmd.NewKey = string(cmd.Args()[1]) | 	cmd.newKey = string(cmd.Args()[1]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *RenameNX) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *RenameNX) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	ok, err := red.Key().RenameNotExists(cmd.Key, cmd.NewKey) | 	ok, err := red.Key().RenameNotExists(cmd.key, cmd.newKey) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,29 +10,25 @@ import ( | |||||||
|  |  | ||||||
| func TestRenameNXParse(t *testing.T) { | func TestRenameNXParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name   string | 		cmd    string | ||||||
| 		args   [][]byte |  | ||||||
| 		key    string | 		key    string | ||||||
| 		newKey string | 		newKey string | ||||||
| 		err    error | 		err    error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name:   "renamenx", | 			cmd:    "renamenx", | ||||||
| 			args:   command.BuildArgs("renamenx"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			newKey: "", | 			newKey: "", | ||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "renamenx name", | 			cmd:    "renamenx name", | ||||||
| 			args:   command.BuildArgs("renamenx", "name"), |  | ||||||
| 			key:    "", | 			key:    "", | ||||||
| 			newKey: "", | 			newKey: "", | ||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "renamenx name title", | 			cmd:    "renamenx name title", | ||||||
| 			args:   command.BuildArgs("renamenx", "name", "title"), |  | ||||||
| 			key:    "name", | 			key:    "name", | ||||||
| 			newKey: "title", | 			newKey: "title", | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| @@ -42,12 +36,12 @@ func TestRenameNXParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseRenameNX, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*key.RenameNX).Key, test.key) | 				testx.AssertEqual(t, cmd.key, test.key) | ||||||
| 				testx.AssertEqual(t, cmd.(*key.RenameNX).NewKey, test.newKey) | 				testx.AssertEqual(t, cmd.newKey, test.newKey) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -60,7 +54,7 @@ func TestRenameNXExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.RenameNX]("renamenx name title") | 		cmd := redis.MustParse(ParseRenameNX, "renamenx name title") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -80,7 +74,7 @@ func TestRenameNXExec(t *testing.T) { | |||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
| 		_ = db.Str().Set("title", "bob") | 		_ = db.Str().Set("title", "bob") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.RenameNX]("renamenx name title") | 		cmd := redis.MustParse(ParseRenameNX, "renamenx name title") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -103,7 +97,7 @@ func TestRenameNXExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.RenameNX]("renamenx name name") | 		cmd := redis.MustParse(ParseRenameNX, "renamenx name name") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -122,7 +116,7 @@ func TestRenameNXExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("title", "bob") | 		_ = db.Str().Set("title", "bob") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*key.RenameNX]("renamenx name title") | 		cmd := redis.MustParse(ParseRenameNX, "renamenx name title") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertEqual(t, err, core.ErrNotFound) | 		testx.AssertEqual(t, err, core.ErrNotFound) | ||||||
|   | |||||||
| @@ -10,33 +10,33 @@ import ( | |||||||
| // https://redis.io/commands/scan | // https://redis.io/commands/scan | ||||||
| type Scan struct { | type Scan struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Cursor int | 	cursor int | ||||||
| 	Match  string | 	match  string | ||||||
| 	Count  int | 	count  int | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseScan(b redis.BaseCmd) (*Scan, error) { | func ParseScan(b redis.BaseCmd) (*Scan, error) { | ||||||
| 	cmd := &Scan{BaseCmd: b} | 	cmd := &Scan{BaseCmd: b} | ||||||
|  |  | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.Int(&cmd.Cursor), | 		parser.Int(&cmd.cursor), | ||||||
| 		parser.Named("match", parser.String(&cmd.Match)), | 		parser.Named("match", parser.String(&cmd.match)), | ||||||
| 		parser.Named("count", parser.Int(&cmd.Count)), | 		parser.Named("count", parser.Int(&cmd.count)), | ||||||
| 	).Required(1).Run(cmd.Args()) | 	).Required(1).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return cmd, err | 		return cmd, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// all keys by default | 	// all keys by default | ||||||
| 	if cmd.Match == "" { | 	if cmd.match == "" { | ||||||
| 		cmd.Match = "*" | 		cmd.match = "*" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *Scan) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *Scan) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	res, err := red.Key().Scan(cmd.Cursor, cmd.Match, cmd.Count) | 	res, err := red.Key().Scan(cmd.cursor, cmd.match, cmd.count) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package key_test | package key | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/key" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/rkey" | 	"github.com/nalgeon/redka/internal/rkey" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,88 +10,77 @@ import ( | |||||||
|  |  | ||||||
| func TestScanParse(t *testing.T) { | func TestScanParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name   string | 		cmd    string | ||||||
| 		args   [][]byte |  | ||||||
| 		cursor int | 		cursor int | ||||||
| 		match  string | 		match  string | ||||||
| 		count  int | 		count  int | ||||||
| 		err    error | 		err    error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name:   "scan", | 			cmd:    "scan", | ||||||
| 			args:   command.BuildArgs("scan"), |  | ||||||
| 			cursor: 0, | 			cursor: 0, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| 			count:  0, | 			count:  0, | ||||||
| 			err:    redis.ErrInvalidArgNum, | 			err:    redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "scan 15", | 			cmd:    "scan 15", | ||||||
| 			args:   command.BuildArgs("scan", "15"), |  | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| 			count:  0, | 			count:  0, | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "scan 15 match *", | 			cmd:    "scan 15 match *", | ||||||
| 			args:   command.BuildArgs("scan", "15", "match", "*"), |  | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| 			count:  0, | 			count:  0, | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "scan 15 match * count 5", | 			cmd:    "scan 15 match * count 5", | ||||||
| 			args:   command.BuildArgs("scan", "15", "match", "*", "count", "5"), |  | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| 			count:  5, | 			count:  5, | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "scan 15 match * count ok", | 			cmd:    "scan 15 match * count ok", | ||||||
| 			args:   command.BuildArgs("scan", "15", "match", "*", "count", "ok"), |  | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| 			count:  0, | 			count:  0, | ||||||
| 			err:    redis.ErrInvalidInt, | 			err:    redis.ErrInvalidInt, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "scan 15 count 5 match *", | 			cmd:    "scan 15 count 5 match *", | ||||||
| 			args:   command.BuildArgs("scan", "15", "count", "5", "match", "*"), |  | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "*", | 			match:  "*", | ||||||
| 			count:  5, | 			count:  5, | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "scan 15 match k2* count 5", | 			cmd:    "scan 15 match k2* count 5", | ||||||
| 			args:   command.BuildArgs("scan", "15", "match", "k2*", "count", "5"), |  | ||||||
| 			cursor: 15, | 			cursor: 15, | ||||||
| 			match:  "k2*", | 			match:  "k2*", | ||||||
| 			count:  5, | 			count:  5, | ||||||
| 			err:    nil, | 			err:    nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "scan ten", | 			cmd:    "scan ten", | ||||||
| 			args:   command.BuildArgs("scan", "ten"), |  | ||||||
| 			cursor: 0, | 			cursor: 0, | ||||||
| 			match:  "", | 			match:  "", | ||||||
| 			count:  0, | 			count:  0, | ||||||
| 			err:    redis.ErrInvalidInt, | 			err:    redis.ErrInvalidInt, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "scan 15 *", | 			cmd:    "scan 15 *", | ||||||
| 			args:   command.BuildArgs("scan", "15", "*"), |  | ||||||
| 			cursor: 0, | 			cursor: 0, | ||||||
| 			match:  "", | 			match:  "", | ||||||
| 			count:  0, | 			count:  0, | ||||||
| 			err:    redis.ErrSyntaxError, | 			err:    redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:   "scan 15 * 5", | 			cmd:    "scan 15 * 5", | ||||||
| 			args:   command.BuildArgs("scan", "15", "*", "5"), |  | ||||||
| 			cursor: 0, | 			cursor: 0, | ||||||
| 			match:  "", | 			match:  "", | ||||||
| 			count:  0, | 			count:  0, | ||||||
| @@ -102,14 +89,13 @@ func TestScanParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseScan, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				scmd := cmd.(*key.Scan) | 				testx.AssertEqual(t, cmd.cursor, test.cursor) | ||||||
| 				testx.AssertEqual(t, scmd.Cursor, test.cursor) | 				testx.AssertEqual(t, cmd.match, test.match) | ||||||
| 				testx.AssertEqual(t, scmd.Match, test.match) | 				testx.AssertEqual(t, cmd.count, test.count) | ||||||
| 				testx.AssertEqual(t, scmd.Count, test.count) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -127,7 +113,7 @@ func TestScanExec(t *testing.T) { | |||||||
|  |  | ||||||
| 	t.Run("scan all", func(t *testing.T) { | 	t.Run("scan all", func(t *testing.T) { | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*key.Scan]("scan 0") | 			cmd := redis.MustParse(ParseScan, "scan 0") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| @@ -141,7 +127,7 @@ func TestScanExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "2,5,5,k11,k12,k21,k22,k31") | 			testx.AssertEqual(t, conn.Out(), "2,5,5,k11,k12,k21,k22,k31") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*key.Scan]("scan 5") | 			cmd := redis.MustParse(ParseScan, "scan 5") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| @@ -155,7 +141,7 @@ func TestScanExec(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("scan pattern", func(t *testing.T) { | 	t.Run("scan pattern", func(t *testing.T) { | ||||||
| 		cmd := command.MustParse[*key.Scan]("scan 0 match k2*") | 		cmd := redis.MustParse(ParseScan, "scan 0 match k2*") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| @@ -172,7 +158,7 @@ func TestScanExec(t *testing.T) { | |||||||
| 	t.Run("scan count", func(t *testing.T) { | 	t.Run("scan count", func(t *testing.T) { | ||||||
| 		{ | 		{ | ||||||
| 			// page 1 | 			// page 1 | ||||||
| 			cmd := command.MustParse[*key.Scan]("scan 0 match * count 2") | 			cmd := redis.MustParse(ParseScan, "scan 0 match * count 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| @@ -187,7 +173,7 @@ func TestScanExec(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			// page 2 | 			// page 2 | ||||||
| 			cmd := command.MustParse[*key.Scan]("scan 2 match * count 2") | 			cmd := redis.MustParse(ParseScan, "scan 2 match * count 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| @@ -202,7 +188,7 @@ func TestScanExec(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			// page 3 | 			// page 3 | ||||||
| 			cmd := command.MustParse[*key.Scan]("scan 4 match * count 2") | 			cmd := redis.MustParse(ParseScan, "scan 4 match * count 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| @@ -216,7 +202,7 @@ func TestScanExec(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			// no more pages | 			// no more pages | ||||||
| 			cmd := command.MustParse[*key.Scan]("scan 5 match * count 2") | 			cmd := redis.MustParse(ParseScan, "scan 5 match * count 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
|  |  | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
|   | |||||||
| @@ -1,60 +1,61 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestDecrParse(t *testing.T) { | func TestDecrParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want Incr | ||||||
| 		want str.Incr |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "decr", | 			cmd:  "decr", | ||||||
| 			args: command.BuildArgs("decr"), | 			want: Incr{}, | ||||||
| 			want: str.Incr{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "decr age", | 			cmd:  "decr age", | ||||||
| 			args: command.BuildArgs("decr", "age"), | 			want: Incr{key: "age", delta: -1}, | ||||||
| 			want: str.Incr{Key: "age", Delta: -1}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "decr age 42", | 			cmd:  "decr age 42", | ||||||
| 			args: command.BuildArgs("decr", "age", "42"), | 			want: Incr{}, | ||||||
| 			want: str.Incr{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	parse := func(b redis.BaseCmd) (*Incr, error) { | ||||||
|  | 		return ParseIncr(b, -1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(parse, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.Incr) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.delta, test.want.delta) | ||||||
| 				testx.AssertEqual(t, cm.Delta, test.want.Delta) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestDecrExec(t *testing.T) { | func TestDecrExec(t *testing.T) { | ||||||
| 	db, red := getDB(t) | 	parse := func(b redis.BaseCmd) (*Incr, error) { | ||||||
| 	defer db.Close() | 		return ParseIncr(b, -1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	t.Run("create", func(t *testing.T) { | 	t.Run("create", func(t *testing.T) { | ||||||
| 		cmd := command.MustParse[*str.Incr]("decr age") | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
|  |  | ||||||
|  | 		cmd := redis.MustParse(parse, "decr age") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -66,9 +67,11 @@ func TestDecrExec(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("decr", func(t *testing.T) { | 	t.Run("decr", func(t *testing.T) { | ||||||
|  | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("age", "25") | 		_ = db.Str().Set("age", "25") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.Incr]("decr age") | 		cmd := redis.MustParse(parse, "decr age") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -1,60 +1,61 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestDecrByParse(t *testing.T) { | func TestDecrByParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want IncrBy | ||||||
| 		want str.IncrBy |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "decrby", | 			cmd:  "decrby", | ||||||
| 			args: command.BuildArgs("decrby"), | 			want: IncrBy{}, | ||||||
| 			want: str.IncrBy{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "decrby age", | 			cmd:  "decrby age", | ||||||
| 			args: command.BuildArgs("decrby", "age"), | 			want: IncrBy{}, | ||||||
| 			want: str.IncrBy{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "decrby age 42", | 			cmd:  "decrby age 42", | ||||||
| 			args: command.BuildArgs("decrby", "age", "42"), | 			want: IncrBy{key: "age", delta: -42}, | ||||||
| 			want: str.IncrBy{Key: "age", Delta: -42}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	parse := func(b redis.BaseCmd) (*IncrBy, error) { | ||||||
|  | 		return ParseIncrBy(b, -1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(parse, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.IncrBy) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.delta, test.want.delta) | ||||||
| 				testx.AssertEqual(t, cm.Delta, test.want.Delta) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestDecrByExec(t *testing.T) { | func TestDecrByExec(t *testing.T) { | ||||||
| 	db, red := getDB(t) | 	parse := func(b redis.BaseCmd) (*IncrBy, error) { | ||||||
| 	defer db.Close() | 		return ParseIncrBy(b, -1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	t.Run("create", func(t *testing.T) { | 	t.Run("create", func(t *testing.T) { | ||||||
| 		cmd := command.MustParse[*str.IncrBy]("decrby age 12") | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
|  |  | ||||||
|  | 		cmd := redis.MustParse(parse, "decrby age 12") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -66,9 +67,11 @@ func TestDecrByExec(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("decrby", func(t *testing.T) { | 	t.Run("decrby", func(t *testing.T) { | ||||||
|  | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("age", "25") | 		_ = db.Str().Set("age", "25") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.IncrBy]("decrby age 12") | 		cmd := redis.MustParse(parse, "decrby age 12") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import ( | |||||||
| // https://redis.io/commands/get | // https://redis.io/commands/get | ||||||
| type Get struct { | type Get struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key string | 	key string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseGet(b redis.BaseCmd) (*Get, error) { | func ParseGet(b redis.BaseCmd) (*Get, error) { | ||||||
| @@ -18,12 +18,12 @@ func ParseGet(b redis.BaseCmd) (*Get, error) { | |||||||
| 	if len(cmd.Args()) != 1 { | 	if len(cmd.Args()) != 1 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *Get) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *Get) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	val, err := red.Str().Get(cmd.Key) | 	val, err := red.Str().Get(cmd.key) | ||||||
| 	if err == core.ErrNotFound { | 	if err == core.ErrNotFound { | ||||||
| 		w.WriteNull() | 		w.WriteNull() | ||||||
| 		return val, nil | 		return val, nil | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,37 +10,33 @@ import ( | |||||||
|  |  | ||||||
| func TestGetParse(t *testing.T) { | func TestGetParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte |  | ||||||
| 		want string | 		want string | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "get", | 			cmd:  "get", | ||||||
| 			args: command.BuildArgs("get"), |  | ||||||
| 			want: "", | 			want: "", | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "get name", | 			cmd:  "get name", | ||||||
| 			args: command.BuildArgs("get", "name"), |  | ||||||
| 			want: "name", | 			want: "name", | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "get name age", | 			cmd:  "get name age", | ||||||
| 			args: command.BuildArgs("get", "name", "age"), |  | ||||||
| 			want: "", | 			want: "", | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseGet, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				testx.AssertEqual(t, cmd.(*str.Get).Key, test.want) | 				testx.AssertEqual(t, cmd.key, test.want) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -55,29 +49,27 @@ func TestGetExec(t *testing.T) { | |||||||
| 	_ = db.Str().Set("name", "alice") | 	_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		cmd  *str.Get | 		res any | ||||||
| 		res  any | 		out string | ||||||
| 		out  string |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "get found", | 			cmd: "get name", | ||||||
| 			cmd:  command.MustParse[*str.Get]("get name"), | 			res: core.Value("alice"), | ||||||
| 			res:  core.Value("alice"), | 			out: "alice", | ||||||
| 			out:  "alice", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "get not found", | 			cmd: "get age", | ||||||
| 			cmd:  command.MustParse[*str.Get]("get age"), | 			res: core.Value(nil), | ||||||
| 			res:  core.Value(nil), | 			out: "(nil)", | ||||||
| 			out:  "(nil)", |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := test.cmd.Run(conn, red) | 			cmd := redis.MustParse(ParseGet, test.cmd) | ||||||
|  | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| 			testx.AssertEqual(t, res, test.res) | 			testx.AssertEqual(t, res, test.res) | ||||||
| 			testx.AssertEqual(t, conn.Out(), test.out) | 			testx.AssertEqual(t, conn.Out(), test.out) | ||||||
|   | |||||||
| @@ -7,8 +7,8 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/getset | // https://redis.io/commands/getset | ||||||
| type GetSet struct { | type GetSet struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Value []byte | 	value []byte | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseGetSet(b redis.BaseCmd) (*GetSet, error) { | func ParseGetSet(b redis.BaseCmd) (*GetSet, error) { | ||||||
| @@ -16,13 +16,13 @@ func ParseGetSet(b redis.BaseCmd) (*GetSet, error) { | |||||||
| 	if len(cmd.Args()) != 2 { | 	if len(cmd.Args()) != 2 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	cmd.Value = cmd.Args()[1] | 	cmd.value = cmd.Args()[1] | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *GetSet) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *GetSet) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	out, err := red.Str().SetWith(cmd.Key, cmd.Value).Run() | 	out, err := red.Str().SetWith(cmd.key, cmd.value).Run() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,45 +10,39 @@ import ( | |||||||
|  |  | ||||||
| func TestGetSetParse(t *testing.T) { | func TestGetSetParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want GetSet | ||||||
| 		want str.GetSet |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "getset", | 			cmd:  "getset", | ||||||
| 			args: command.BuildArgs("getset"), | 			want: GetSet{}, | ||||||
| 			want: str.GetSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "getset name", | 			cmd:  "getset name", | ||||||
| 			args: command.BuildArgs("getset", "name"), | 			want: GetSet{}, | ||||||
| 			want: str.GetSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "getset name alice", | 			cmd:  "getset name alice", | ||||||
| 			args: command.BuildArgs("getset", "name", "alice"), | 			want: GetSet{key: "name", value: []byte("alice")}, | ||||||
| 			want: str.GetSet{Key: "name", Value: []byte("alice")}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "getset name alice 60", | 			cmd:  "getset name alice 60", | ||||||
| 			args: command.BuildArgs("getset", "name", "alice", "60"), | 			want: GetSet{}, | ||||||
| 			want: str.GetSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseGetSet, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.GetSet) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.value, test.want.value) | ||||||
| 				testx.AssertEqual(t, cm.Value, test.want.Value) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -61,7 +53,7 @@ func TestGetSetExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.GetSet]("getset name alice") | 		cmd := redis.MustParse(ParseGetSet, "getset name alice") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -78,7 +70,7 @@ func TestGetSetExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.GetSet]("getset name bob") | 		cmd := redis.MustParse(ParseGetSet, "getset name bob") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/decr | // https://redis.io/commands/decr | ||||||
| type Incr struct { | type Incr struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Delta int | 	delta int | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseIncr(b redis.BaseCmd, sign int) (*Incr, error) { | func ParseIncr(b redis.BaseCmd, sign int) (*Incr, error) { | ||||||
| @@ -22,13 +22,13 @@ func ParseIncr(b redis.BaseCmd, sign int) (*Incr, error) { | |||||||
| 	if len(cmd.Args()) != 1 { | 	if len(cmd.Args()) != 1 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	cmd.Delta = sign | 	cmd.delta = sign | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *Incr) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *Incr) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	val, err := red.Str().Incr(cmd.Key, cmd.Delta) | 	val, err := red.Str().Incr(cmd.key, cmd.delta) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,60 +1,61 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestIncrParse(t *testing.T) { | func TestIncrParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want Incr | ||||||
| 		want str.Incr |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "incr", | 			cmd:  "incr", | ||||||
| 			args: command.BuildArgs("incr"), | 			want: Incr{}, | ||||||
| 			want: str.Incr{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "incr age", | 			cmd:  "incr age", | ||||||
| 			args: command.BuildArgs("incr", "age"), | 			want: Incr{key: "age", delta: 1}, | ||||||
| 			want: str.Incr{Key: "age", Delta: 1}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "incr age 42", | 			cmd:  "incr age 42", | ||||||
| 			args: command.BuildArgs("incr", "age", "42"), | 			want: Incr{}, | ||||||
| 			want: str.Incr{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	parse := func(b redis.BaseCmd) (*Incr, error) { | ||||||
|  | 		return ParseIncr(b, 1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(parse, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.Incr) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.delta, test.want.delta) | ||||||
| 				testx.AssertEqual(t, cm.Delta, test.want.Delta) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestIncrExec(t *testing.T) { | func TestIncrExec(t *testing.T) { | ||||||
| 	db, red := getDB(t) | 	parse := func(b redis.BaseCmd) (*Incr, error) { | ||||||
| 	defer db.Close() | 		return ParseIncr(b, 1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	t.Run("create", func(t *testing.T) { | 	t.Run("create", func(t *testing.T) { | ||||||
| 		cmd := command.MustParse[*str.Incr]("incr age") | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
|  |  | ||||||
|  | 		cmd := redis.MustParse(parse, "incr age") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -66,9 +67,11 @@ func TestIncrExec(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("incr", func(t *testing.T) { | 	t.Run("incr", func(t *testing.T) { | ||||||
|  | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("age", "25") | 		_ = db.Str().Set("age", "25") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.Incr]("incr age") | 		cmd := redis.MustParse(parse, "incr age") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -16,25 +16,25 @@ import ( | |||||||
| // https://redis.io/commands/decrby | // https://redis.io/commands/decrby | ||||||
| type IncrBy struct { | type IncrBy struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Delta int | 	delta int | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseIncrBy(b redis.BaseCmd, sign int) (*IncrBy, error) { | func ParseIncrBy(b redis.BaseCmd, sign int) (*IncrBy, error) { | ||||||
| 	cmd := &IncrBy{BaseCmd: b} | 	cmd := &IncrBy{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Int(&cmd.Delta), | 		parser.Int(&cmd.delta), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	cmd.Delta *= sign | 	cmd.delta *= sign | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *IncrBy) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *IncrBy) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	val, err := red.Str().Incr(cmd.Key, cmd.Delta) | 	val, err := red.Str().Incr(cmd.key, cmd.delta) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,60 +1,61 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestIncrByParse(t *testing.T) { | func TestIncrByParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want IncrBy | ||||||
| 		want str.IncrBy |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "incrby", | 			cmd:  "incrby", | ||||||
| 			args: command.BuildArgs("incrby"), | 			want: IncrBy{}, | ||||||
| 			want: str.IncrBy{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "incrby age", | 			cmd:  "incrby age", | ||||||
| 			args: command.BuildArgs("incrby", "age"), | 			want: IncrBy{}, | ||||||
| 			want: str.IncrBy{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "incrby age 42", | 			cmd:  "incrby age 42", | ||||||
| 			args: command.BuildArgs("incrby", "age", "42"), | 			want: IncrBy{key: "age", delta: 42}, | ||||||
| 			want: str.IncrBy{Key: "age", Delta: 42}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	parse := func(b redis.BaseCmd) (*IncrBy, error) { | ||||||
|  | 		return ParseIncrBy(b, 1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(parse, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.IncrBy) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.delta, test.want.delta) | ||||||
| 				testx.AssertEqual(t, cm.Delta, test.want.Delta) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestIncrByExec(t *testing.T) { | func TestIncrByExec(t *testing.T) { | ||||||
| 	db, red := getDB(t) | 	parse := func(b redis.BaseCmd) (*IncrBy, error) { | ||||||
| 	defer db.Close() | 		return ParseIncrBy(b, 1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	t.Run("create", func(t *testing.T) { | 	t.Run("create", func(t *testing.T) { | ||||||
| 		cmd := command.MustParse[*str.IncrBy]("incrby age 42") | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
|  |  | ||||||
|  | 		cmd := redis.MustParse(parse, "incrby age 42") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -66,9 +67,11 @@ func TestIncrByExec(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("incrby", func(t *testing.T) { | 	t.Run("incrby", func(t *testing.T) { | ||||||
|  | 		db, red := getDB(t) | ||||||
|  | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("age", "25") | 		_ = db.Str().Set("age", "25") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.IncrBy]("incrby age 42") | 		cmd := redis.MustParse(parse, "incrby age 42") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -1,8 +1,6 @@ | |||||||
| package string | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"strconv" |  | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/parser" | 	"github.com/nalgeon/redka/internal/parser" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| ) | ) | ||||||
| @@ -13,15 +11,15 @@ import ( | |||||||
| // https://redis.io/commands/incrbyfloat | // https://redis.io/commands/incrbyfloat | ||||||
| type IncrByFloat struct { | type IncrByFloat struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Delta float64 | 	delta float64 | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseIncrByFloat(b redis.BaseCmd) (*IncrByFloat, error) { | func ParseIncrByFloat(b redis.BaseCmd) (*IncrByFloat, error) { | ||||||
| 	cmd := &IncrByFloat{BaseCmd: b} | 	cmd := &IncrByFloat{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Float(&cmd.Delta), | 		parser.Float(&cmd.delta), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -30,11 +28,11 @@ func ParseIncrByFloat(b redis.BaseCmd) (*IncrByFloat, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *IncrByFloat) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *IncrByFloat) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	val, err := red.Str().IncrFloat(cmd.Key, cmd.Delta) | 	val, err := red.Str().IncrFloat(cmd.key, cmd.delta) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	w.WriteBulkString(strconv.FormatFloat(val, 'f', -1, 64)) | 	redis.WriteFloat(w, val) | ||||||
| 	return val, nil | 	return val, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,61 +1,52 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestIncrByFloatParse(t *testing.T) { | func TestIncrByFloatParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want IncrByFloat | ||||||
| 		want str.IncrByFloat |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "incrbyfloat", | 			cmd:  "incrbyfloat", | ||||||
| 			args: command.BuildArgs("incrbyfloat"), | 			want: IncrByFloat{}, | ||||||
| 			want: str.IncrByFloat{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "incrbyfloat age", | 			cmd:  "incrbyfloat age", | ||||||
| 			args: command.BuildArgs("incrbyfloat", "age"), | 			want: IncrByFloat{}, | ||||||
| 			want: str.IncrByFloat{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "incrbyfloat age 4.2", | 			cmd:  "incrbyfloat age 4.2", | ||||||
| 			args: command.BuildArgs("incrbyfloat", "age", "4.2"), | 			want: IncrByFloat{key: "age", delta: 4.2}, | ||||||
| 			want: str.IncrByFloat{Key: "age", Delta: 4.2}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "incrbyfloat age -4.2", | 			cmd:  "incrbyfloat age -4.2", | ||||||
| 			args: command.BuildArgs("incrbyfloat", "age", "4.2"), | 			want: IncrByFloat{key: "age", delta: -4.2}, | ||||||
| 			want: str.IncrByFloat{Key: "age", Delta: 4.2}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "incrbyfloat age 2.0e2", | 			cmd:  "incrbyfloat age 2.0e2", | ||||||
| 			args: command.BuildArgs("incrbyfloat", "age", "2.0e2"), | 			want: IncrByFloat{key: "age", delta: 2.0e2}, | ||||||
| 			want: str.IncrByFloat{Key: "age", Delta: 2.0e2}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseIncrByFloat, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.IncrByFloat) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.delta, test.want.delta) | ||||||
| 				testx.AssertEqual(t, cm.Delta, test.want.Delta) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -66,43 +57,39 @@ func TestIncrByFloatExec(t *testing.T) { | |||||||
| 	defer db.Close() | 	defer db.Close() | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		cmd  *str.IncrByFloat | 		res any | ||||||
| 		res  any | 		out string | ||||||
| 		out  string |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "positive", | 			cmd: "incrbyfloat age 4.2", | ||||||
| 			cmd:  command.MustParse[*str.IncrByFloat]("incrbyfloat age 4.2"), | 			res: 29.2, | ||||||
| 			res:  29.2, | 			out: "29.2", | ||||||
| 			out:  "29.2", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "negative", | 			cmd: "incrbyfloat age -4.2", | ||||||
| 			cmd:  command.MustParse[*str.IncrByFloat]("incrbyfloat age -4.2"), | 			res: 20.8, | ||||||
| 			res:  20.8, | 			out: "20.8", | ||||||
| 			out:  "20.8", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zero", | 			cmd: "incrbyfloat age 0", | ||||||
| 			cmd:  command.MustParse[*str.IncrByFloat]("incrbyfloat age 0"), | 			res: 25.0, | ||||||
| 			res:  25.0, | 			out: "25", | ||||||
| 			out:  "25", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "exponential", | 			cmd: "incrbyfloat age 2.0e2", | ||||||
| 			cmd:  command.MustParse[*str.IncrByFloat]("incrbyfloat age 2.0e2"), | 			res: 225.0, | ||||||
| 			res:  225.0, | 			out: "225", | ||||||
| 			out:  "225", |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			_ = db.Str().Set("age", 25) | 			_ = db.Str().Set("age", 25) | ||||||
|  |  | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := test.cmd.Run(conn, red) | 			cmd := redis.MustParse(ParseIncrByFloat, test.cmd) | ||||||
|  | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| 			testx.AssertEqual(t, res, test.res) | 			testx.AssertEqual(t, res, test.res) | ||||||
| 			testx.AssertEqual(t, conn.Out(), test.out) | 			testx.AssertEqual(t, conn.Out(), test.out) | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import ( | |||||||
| // https://redis.io/commands/mget | // https://redis.io/commands/mget | ||||||
| type MGet struct { | type MGet struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Keys []string | 	keys []string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseMGet(b redis.BaseCmd) (*MGet, error) { | func ParseMGet(b redis.BaseCmd) (*MGet, error) { | ||||||
| @@ -18,16 +18,16 @@ func ParseMGet(b redis.BaseCmd) (*MGet, error) { | |||||||
| 	if len(cmd.Args()) < 1 { | 	if len(cmd.Args()) < 1 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Keys = make([]string, len(cmd.Args())) | 	cmd.keys = make([]string, len(cmd.Args())) | ||||||
| 	for i, arg := range cmd.Args() { | 	for i, arg := range cmd.Args() { | ||||||
| 		cmd.Keys[i] = string(arg) | 		cmd.keys[i] = string(arg) | ||||||
| 	} | 	} | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *MGet) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *MGet) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	// Get the key-value map for requested keys. | 	// Get the key-value map for requested keys. | ||||||
| 	items, err := red.Str().GetMany(cmd.Keys...) | 	items, err := red.Str().GetMany(cmd.keys...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -36,8 +36,8 @@ func (cmd *MGet) Run(w redis.Writer, red redis.Redka) (any, error) { | |||||||
| 	// Build the result slice. | 	// Build the result slice. | ||||||
| 	// It will contain all values in the order of keys. | 	// It will contain all values in the order of keys. | ||||||
| 	// Missing keys will have nil values. | 	// Missing keys will have nil values. | ||||||
| 	vals := make([]core.Value, len(cmd.Keys)) | 	vals := make([]core.Value, len(cmd.keys)) | ||||||
| 	for i, key := range cmd.Keys { | 	for i, key := range cmd.keys { | ||||||
| 		vals[i] = items[key] | 		vals[i] = items[key] | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,38 +10,33 @@ import ( | |||||||
|  |  | ||||||
| func TestMGetParse(t *testing.T) { | func TestMGetParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte |  | ||||||
| 		want []string | 		want []string | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "mget", | 			cmd:  "mget", | ||||||
| 			args: command.BuildArgs("mget"), |  | ||||||
| 			want: nil, | 			want: nil, | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "mget name", | 			cmd:  "mget name", | ||||||
| 			args: command.BuildArgs("mget", "name"), |  | ||||||
| 			want: []string{"name"}, | 			want: []string{"name"}, | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "mget name age", | 			cmd:  "mget name age", | ||||||
| 			args: command.BuildArgs("mget", "name", "age"), |  | ||||||
| 			want: []string{"name", "age"}, | 			want: []string{"name", "age"}, | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseMGet, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.MGet) | 				testx.AssertEqual(t, cmd.keys, test.want) | ||||||
| 				testx.AssertEqual(t, cm.Keys, test.want) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -57,41 +50,37 @@ func TestMGetExec(t *testing.T) { | |||||||
| 	_ = db.Str().Set("age", 25) | 	_ = db.Str().Set("age", 25) | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		cmd  *str.MGet | 		res any | ||||||
| 		res  any | 		out string | ||||||
| 		out  string |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "single key", | 			cmd: "mget name", | ||||||
| 			cmd:  command.MustParse[*str.MGet]("mget name"), | 			res: []core.Value{core.Value("alice")}, | ||||||
| 			res:  []core.Value{core.Value("alice")}, | 			out: "1,alice", | ||||||
| 			out:  "1,alice", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "multiple keys", | 			cmd: "mget name age", | ||||||
| 			cmd:  command.MustParse[*str.MGet]("mget name age"), | 			res: []core.Value{core.Value("alice"), core.Value("25")}, | ||||||
| 			res:  []core.Value{core.Value("alice"), core.Value("25")}, | 			out: "2,alice,25", | ||||||
| 			out:  "2,alice,25", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "some not found", | 			cmd: "mget name city age", | ||||||
| 			cmd:  command.MustParse[*str.MGet]("mget name city age"), | 			res: []core.Value{core.Value("alice"), core.Value(nil), core.Value("25")}, | ||||||
| 			res:  []core.Value{core.Value("alice"), core.Value(nil), core.Value("25")}, | 			out: "3,alice,(nil),25", | ||||||
| 			out:  "3,alice,(nil),25", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "all not found", | 			cmd: "mget one two", | ||||||
| 			cmd:  command.MustParse[*str.MGet]("mget one two"), | 			res: []core.Value{core.Value(nil), core.Value(nil)}, | ||||||
| 			res:  []core.Value{core.Value(nil), core.Value(nil)}, | 			out: "2,(nil),(nil)", | ||||||
| 			out:  "2,(nil),(nil)", |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := test.cmd.Run(conn, red) | 			cmd := redis.MustParse(ParseMGet, test.cmd) | ||||||
|  | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| 			testx.AssertEqual(t, res, test.res) | 			testx.AssertEqual(t, res, test.res) | ||||||
| 			testx.AssertEqual(t, conn.Out(), test.out) | 			testx.AssertEqual(t, conn.Out(), test.out) | ||||||
|   | |||||||
| @@ -10,13 +10,13 @@ import ( | |||||||
| // https://redis.io/commands/mset | // https://redis.io/commands/mset | ||||||
| type MSet struct { | type MSet struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Items map[string]any | 	items map[string]any | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseMSet(b redis.BaseCmd) (*MSet, error) { | func ParseMSet(b redis.BaseCmd) (*MSet, error) { | ||||||
| 	cmd := &MSet{BaseCmd: b} | 	cmd := &MSet{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.AnyMap(&cmd.Items), | 		parser.AnyMap(&cmd.items), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -25,7 +25,7 @@ func ParseMSet(b redis.BaseCmd) (*MSet, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *MSet) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *MSet) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	err := red.Str().SetMany(cmd.Items) | 	err := red.Str().SetMany(cmd.items) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,49 +1,41 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestMSetParse(t *testing.T) { | func TestMSetParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want MSet | ||||||
| 		want str.MSet |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "mset", | 			cmd:  "mset", | ||||||
| 			args: command.BuildArgs("mset"), | 			want: MSet{}, | ||||||
| 			want: str.MSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "mset name", | 			cmd:  "mset name", | ||||||
| 			args: command.BuildArgs("mset", "name"), | 			want: MSet{}, | ||||||
| 			want: str.MSet{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "mset name alice", | 			cmd:  "mset name alice", | ||||||
| 			args: command.BuildArgs("mset", "name", "alice"), | 			want: MSet{items: map[string]any{"name": []byte("alice")}}, | ||||||
| 			want: str.MSet{Items: map[string]any{"name": []byte("alice")}}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "mset name alice age", | 			cmd:  "mset name alice age", | ||||||
| 			args: command.BuildArgs("mset", "name", "alice", "age"), | 			want: MSet{}, | ||||||
| 			want: str.MSet{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "mset name alice age 25", | 			cmd: "mset name alice age 25", | ||||||
| 			args: command.BuildArgs("mset", "name", "alice", "age", "25"), | 			want: MSet{items: map[string]any{ | ||||||
| 			want: str.MSet{Items: map[string]any{ |  | ||||||
| 				"name": []byte("alice"), | 				"name": []byte("alice"), | ||||||
| 				"age":  []byte("25"), | 				"age":  []byte("25"), | ||||||
| 			}}, | 			}}, | ||||||
| @@ -52,12 +44,11 @@ func TestMSetParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseMSet, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.MSet) | 				testx.AssertEqual(t, cmd.items, test.want.items) | ||||||
| 				testx.AssertEqual(t, cm.Items, test.want.Items) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -68,7 +59,7 @@ func TestMSetExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.MSet]("mset name alice") | 		cmd := redis.MustParse(ParseMSet, "mset name alice") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -83,7 +74,7 @@ func TestMSetExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.MSet]("mset name alice age 25") | 		cmd := redis.MustParse(ParseMSet, "mset name alice age 25") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -102,7 +93,7 @@ func TestMSetExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.MSet]("mset name bob age 50") | 		cmd := redis.MustParse(ParseMSet, "mset name bob age 50") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -122,7 +113,7 @@ func TestMSetExec(t *testing.T) { | |||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
| 		_ = db.Str().Set("age", 25) | 		_ = db.Str().Set("age", 25) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.MSet]("mset name bob age 50") | 		cmd := redis.MustParse(ParseMSet, "mset name bob age 50") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -1,74 +1,73 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestPSetEXParse(t *testing.T) { | func TestPSetEXParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want SetEX | ||||||
| 		want str.SetEX |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "psetex", | 			cmd:  "psetex", | ||||||
| 			args: command.BuildArgs("psetex"), | 			want: SetEX{}, | ||||||
| 			want: str.SetEX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "psetex name", | 			cmd:  "psetex name", | ||||||
| 			args: command.BuildArgs("psetex", "name"), | 			want: SetEX{}, | ||||||
| 			want: str.SetEX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "psetex name alice", | 			cmd:  "psetex name alice", | ||||||
| 			args: command.BuildArgs("psetex", "name", "alice"), | 			want: SetEX{}, | ||||||
| 			want: str.SetEX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "psetex name alice 60", | 			cmd:  "psetex name alice 60", | ||||||
| 			args: command.BuildArgs("psetex", "name", "alice", "60"), | 			want: SetEX{}, | ||||||
| 			want: str.SetEX{}, |  | ||||||
| 			err:  redis.ErrInvalidInt, | 			err:  redis.ErrInvalidInt, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "psetex name 60 alice", | 			cmd:  "psetex name 60 alice", | ||||||
| 			args: command.BuildArgs("psetex", "name", "60", "alice"), | 			want: SetEX{key: "name", value: []byte("alice"), ttl: 60 * time.Millisecond}, | ||||||
| 			want: str.SetEX{Key: "name", Value: []byte("alice"), TTL: 60 * time.Millisecond}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	parse := func(b redis.BaseCmd) (*SetEX, error) { | ||||||
|  | 		return ParseSetEX(b, 1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(parse, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.SetEX) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.value, test.want.value) | ||||||
| 				testx.AssertEqual(t, cm.Value, test.want.Value) | 				testx.AssertEqual(t, cmd.ttl, test.want.ttl) | ||||||
| 				testx.AssertEqual(t, cm.TTL, test.want.TTL) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestPSetEXExec(t *testing.T) { | func TestPSetEXExec(t *testing.T) { | ||||||
|  | 	parse := func(b redis.BaseCmd) (*SetEX, error) { | ||||||
|  | 		return ParseSetEX(b, 1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	t.Run("create", func(t *testing.T) { | 	t.Run("create", func(t *testing.T) { | ||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.SetEX]("psetex name 60000 alice") | 		cmd := redis.MustParse(parse, "psetex name 60000 alice") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -89,7 +88,7 @@ func TestPSetEXExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.SetEX]("psetex name 60000 bob") | 		cmd := redis.MustParse(parse, "psetex name 60000 bob") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -110,7 +109,7 @@ func TestPSetEXExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().SetExpires("name", "alice", 60*time.Second) | 		_ = db.Str().SetExpires("name", "alice", 60*time.Second) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.SetEX]("psetex name 10000 bob") | 		cmd := redis.MustParse(parse, "psetex name 10000 bob") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -14,14 +14,14 @@ import ( | |||||||
| // https://redis.io/commands/set | // https://redis.io/commands/set | ||||||
| type Set struct { | type Set struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key     string | 	key     string | ||||||
| 	Value   []byte | 	value   []byte | ||||||
| 	IfNX    bool | 	ifNX    bool | ||||||
| 	IfXX    bool | 	ifXX    bool | ||||||
| 	Get     bool | 	get     bool | ||||||
| 	TTL     time.Duration | 	ttl     time.Duration | ||||||
| 	At      time.Time | 	at      time.Time | ||||||
| 	KeepTTL bool | 	keepTTL bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseSet(b redis.BaseCmd) (*Set, error) { | func ParseSet(b redis.BaseCmd) (*Set, error) { | ||||||
| @@ -30,19 +30,19 @@ func ParseSet(b redis.BaseCmd) (*Set, error) { | |||||||
| 	// Parse the command arguments. | 	// Parse the command arguments. | ||||||
| 	var ttlSec, ttlMs, atSec, atMs int | 	var ttlSec, ttlMs, atSec, atMs int | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Bytes(&cmd.Value), | 		parser.Bytes(&cmd.value), | ||||||
| 		parser.OneOf( | 		parser.OneOf( | ||||||
| 			parser.Flag("nx", &cmd.IfNX), | 			parser.Flag("nx", &cmd.ifNX), | ||||||
| 			parser.Flag("xx", &cmd.IfXX), | 			parser.Flag("xx", &cmd.ifXX), | ||||||
| 		), | 		), | ||||||
| 		parser.Flag("get", &cmd.Get), | 		parser.Flag("get", &cmd.get), | ||||||
| 		parser.OneOf( | 		parser.OneOf( | ||||||
| 			parser.Named("ex", parser.Int(&ttlSec)), | 			parser.Named("ex", parser.Int(&ttlSec)), | ||||||
| 			parser.Named("px", parser.Int(&ttlMs)), | 			parser.Named("px", parser.Int(&ttlMs)), | ||||||
| 			parser.Named("exat", parser.Int(&atSec)), | 			parser.Named("exat", parser.Int(&atSec)), | ||||||
| 			parser.Named("pxat", parser.Int(&atMs)), | 			parser.Named("pxat", parser.Int(&atMs)), | ||||||
| 			parser.Flag("keepttl", &cmd.KeepTTL), | 			parser.Flag("keepttl", &cmd.keepTTL), | ||||||
| 		), | 		), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -51,15 +51,15 @@ func ParseSet(b redis.BaseCmd) (*Set, error) { | |||||||
|  |  | ||||||
| 	// Set the expiration time. | 	// Set the expiration time. | ||||||
| 	if ttlSec > 0 { | 	if ttlSec > 0 { | ||||||
| 		cmd.TTL = time.Duration(ttlSec) * time.Second | 		cmd.ttl = time.Duration(ttlSec) * time.Second | ||||||
| 	} else if ttlMs > 0 { | 	} else if ttlMs > 0 { | ||||||
| 		cmd.TTL = time.Duration(ttlMs) * time.Millisecond | 		cmd.ttl = time.Duration(ttlMs) * time.Millisecond | ||||||
| 	} else if atSec > 0 { | 	} else if atSec > 0 { | ||||||
| 		cmd.At = time.Unix(int64(atSec), 0) | 		cmd.at = time.Unix(int64(atSec), 0) | ||||||
| 	} else if atMs > 0 { | 	} else if atMs > 0 { | ||||||
| 		cmd.At = time.Unix(0, int64(atMs)*int64(time.Millisecond)) | 		cmd.at = time.Unix(0, int64(atMs)*int64(time.Millisecond)) | ||||||
| 	} | 	} | ||||||
| 	if cmd.TTL < 0 { | 	if cmd.ttl < 0 { | ||||||
| 		return cmd, redis.ErrInvalidExpireTime | 		return cmd, redis.ErrInvalidExpireTime | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -67,9 +67,9 @@ func ParseSet(b redis.BaseCmd) (*Set, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *Set) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *Set) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	if !cmd.IfNX && !cmd.IfXX && !cmd.Get && !cmd.KeepTTL && cmd.At.IsZero() { | 	if !cmd.ifNX && !cmd.ifXX && !cmd.get && !cmd.keepTTL && cmd.at.IsZero() { | ||||||
| 		// Simple SET without additional options (except ttl). | 		// Simple SET without additional options (except ttl). | ||||||
| 		err := red.Str().SetExpires(cmd.Key, cmd.Value, cmd.TTL) | 		err := red.Str().SetExpires(cmd.key, cmd.value, cmd.ttl) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			w.WriteError(cmd.Error(err)) | 			w.WriteError(cmd.Error(err)) | ||||||
| 			return nil, err | 			return nil, err | ||||||
| @@ -79,26 +79,26 @@ func (cmd *Set) Run(w redis.Writer, red redis.Redka) (any, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// SET with additional options. | 	// SET with additional options. | ||||||
| 	op := red.Str().SetWith(cmd.Key, cmd.Value) | 	op := red.Str().SetWith(cmd.key, cmd.value) | ||||||
| 	if cmd.IfXX { | 	if cmd.ifXX { | ||||||
| 		op = op.IfExists() | 		op = op.IfExists() | ||||||
| 	} else if cmd.IfNX { | 	} else if cmd.ifNX { | ||||||
| 		op = op.IfNotExists() | 		op = op.IfNotExists() | ||||||
| 	} | 	} | ||||||
| 	if cmd.TTL > 0 { | 	if cmd.ttl > 0 { | ||||||
| 		op = op.TTL(cmd.TTL) | 		op = op.TTL(cmd.ttl) | ||||||
| 	} else if !cmd.At.IsZero() { | 	} else if !cmd.at.IsZero() { | ||||||
| 		op = op.At(cmd.At) | 		op = op.At(cmd.at) | ||||||
| 	} else if cmd.KeepTTL { | 	} else if cmd.keepTTL { | ||||||
| 		op = op.KeepTTL() | 		op = op.KeepTTL() | ||||||
| 	} | 	} | ||||||
| 	out, err := op.Run() | 	out, err := op.Run() | ||||||
|  |  | ||||||
| 	// Determine the output status. | 	// Determine the output status. | ||||||
| 	var ok bool | 	var ok bool | ||||||
| 	if cmd.IfXX { | 	if cmd.ifXX { | ||||||
| 		ok = out.Updated | 		ok = out.Updated | ||||||
| 	} else if cmd.IfNX { | 	} else if cmd.ifNX { | ||||||
| 		ok = out.Created | 		ok = out.Created | ||||||
| 	} else { | 	} else { | ||||||
| 		ok = err == nil | 		ok = err == nil | ||||||
| @@ -110,7 +110,7 @@ func (cmd *Set) Run(w redis.Writer, red redis.Redka) (any, error) { | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if cmd.Get { | 	if cmd.get { | ||||||
| 		// GET given: The key didn't exist before the SET. | 		// GET given: The key didn't exist before the SET. | ||||||
| 		if !out.Prev.Exists() { | 		if !out.Prev.Exists() { | ||||||
| 			w.WriteNull() | 			w.WriteNull() | ||||||
|   | |||||||
| @@ -1,11 +1,9 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -13,128 +11,125 @@ import ( | |||||||
|  |  | ||||||
| func TestSetParse(t *testing.T) { | func TestSetParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want Set | ||||||
| 		want str.Set |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "set", | 			cmd:  "set", | ||||||
| 			args: command.BuildArgs("set"), | 			want: Set{}, | ||||||
| 			want: str.Set{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name", | 			cmd:  "set name", | ||||||
| 			args: command.BuildArgs("set", "name"), | 			want: Set{}, | ||||||
| 			want: str.Set{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice", | 			cmd:  "set name alice", | ||||||
| 			args: command.BuildArgs("set", "name", "alice"), | 			want: Set{key: "name", value: []byte("alice")}, | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice")}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice nx", | 			cmd:  "set name alice nx", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "nx"), | 			want: Set{key: "name", value: []byte("alice"), ifNX: true}, | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), IfNX: true}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice xx", | 			cmd:  "set name alice xx", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "xx"), | 			want: Set{key: "name", value: []byte("alice"), ifXX: true}, | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), IfXX: true}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice nx xx", | 			cmd:  "set name alice nx xx", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "nx", "xx"), | 			want: Set{}, | ||||||
| 			want: str.Set{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice ex 10", | 			cmd:  "set name alice ex 10", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "ex", "10"), | 			want: Set{key: "name", value: []byte("alice"), ttl: 10 * time.Second}, | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), TTL: 10 * time.Second}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice ex 0", | 			cmd:  "set name alice ex 0", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "ex", "0"), | 			want: Set{key: "name", value: []byte("alice"), ttl: 0}, | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), TTL: 0}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice px 10", | 			cmd:  "set name alice px 10", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "px", "10"), | 			want: Set{key: "name", value: []byte("alice"), ttl: 10 * time.Millisecond}, | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), TTL: 10 * time.Millisecond}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice exat 1577882096", | 			cmd: "set name alice exat 1577882096", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "exat", "1577882096"), | 			want: Set{ | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), | 				key: "name", value: []byte("alice"), | ||||||
| 				At: time.Date(2020, 1, 1, 12, 34, 56, 0, time.UTC)}, | 				at: time.Date(2020, 1, 1, 12, 34, 56, 0, time.UTC), | ||||||
|  | 			}, | ||||||
| 			err: nil, | 			err: nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice pxat 1577882096000", | 			cmd: "set name alice pxat 1577882096000", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "exat", "1577882096000"), | 			want: Set{ | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), | 				key: "name", value: []byte("alice"), | ||||||
| 				At: time.Date(2020, 1, 1, 12, 34, 56, 0, time.UTC)}, | 				at: time.Date(2020, 1, 1, 12, 34, 56, 0, time.UTC), | ||||||
|  | 			}, | ||||||
| 			err: nil, | 			err: nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice keepttl", | 			cmd:  "set name alice keepttl", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "keepttl"), | 			want: Set{key: "name", value: []byte("alice"), keepTTL: true}, | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), KeepTTL: true}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice ex 10 keepttl", | 			cmd:  "set name alice ex 10 keepttl", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "ex", "10", "keepttl"), | 			want: Set{}, | ||||||
| 			want: str.Set{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice nx ex 10", | 			cmd: "set name alice nx ex 10", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "nx", "ex", "10"), | 			want: Set{ | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), IfNX: true, TTL: 10 * time.Second}, | 				key: "name", value: []byte("alice"), | ||||||
| 			err:  nil, | 				ifNX: true, ttl: 10 * time.Second, | ||||||
|  | 			}, | ||||||
|  | 			err: nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice xx px 10", | 			cmd: "set name alice xx px 10", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "xx", "px", "10"), | 			want: Set{ | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), IfXX: true, TTL: 10 * time.Millisecond}, | 				key: "name", value: []byte("alice"), | ||||||
| 			err:  nil, | 				ifXX: true, ttl: 10 * time.Millisecond, | ||||||
|  | 			}, | ||||||
|  | 			err: nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice ex 10 nx", | 			cmd: "set name alice ex 10 nx", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "ex", "10", "nx"), | 			want: Set{ | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), IfNX: true, TTL: 10 * time.Second}, | 				key: "name", value: []byte("alice"), | ||||||
| 			err:  nil, | 				ifNX: true, ttl: 10 * time.Second, | ||||||
|  | 			}, | ||||||
|  | 			err: nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set name alice nx get ex 10", | 			cmd: "set name alice nx get ex 10", | ||||||
| 			args: command.BuildArgs("set", "name", "alice", "nx", "ex", "10"), | 			want: Set{ | ||||||
| 			want: str.Set{Key: "name", Value: []byte("alice"), IfNX: true, Get: true, TTL: 10 * time.Second}, | 				key: "name", value: []byte("alice"), | ||||||
| 			err:  nil, | 				ifNX: true, get: true, ttl: 10 * time.Second, | ||||||
|  | 			}, | ||||||
|  | 			err: nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseSet, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				setCmd := cmd.(*str.Set) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, setCmd.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.value, test.want.value) | ||||||
| 				testx.AssertEqual(t, setCmd.Value, test.want.Value) | 				testx.AssertEqual(t, cmd.ifNX, test.want.ifNX) | ||||||
| 				testx.AssertEqual(t, setCmd.IfNX, test.want.IfNX) | 				testx.AssertEqual(t, cmd.ifXX, test.want.ifXX) | ||||||
| 				testx.AssertEqual(t, setCmd.IfXX, test.want.IfXX) | 				testx.AssertEqual(t, cmd.ttl, test.want.ttl) | ||||||
| 				testx.AssertEqual(t, setCmd.TTL, test.want.TTL) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -145,77 +140,67 @@ func TestSetExec(t *testing.T) { | |||||||
| 	defer db.Close() | 	defer db.Close() | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd string | ||||||
| 		cmd  *str.Set | 		res any | ||||||
| 		res  any | 		out string | ||||||
| 		out  string |  | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "set", | 			cmd: "set name alice", | ||||||
| 			cmd:  command.MustParse[*str.Set]("set name alice"), | 			res: true, | ||||||
| 			res:  true, | 			out: "OK", | ||||||
| 			out:  "OK", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set nx conflict", | 			cmd: "set name alice nx", | ||||||
| 			cmd:  command.MustParse[*str.Set]("set name alice nx"), | 			res: false, | ||||||
| 			res:  false, | 			out: "(nil)", | ||||||
| 			out:  "(nil)", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set nx", | 			cmd: "set age alice nx", | ||||||
| 			cmd:  command.MustParse[*str.Set]("set age alice nx"), | 			res: true, | ||||||
| 			res:  true, | 			out: "OK", | ||||||
| 			out:  "OK", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set xx", | 			cmd: "set name bob xx", | ||||||
| 			cmd:  command.MustParse[*str.Set]("set name bob xx"), | 			res: true, | ||||||
| 			res:  true, | 			out: "OK", | ||||||
| 			out:  "OK", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set xx conflict", | 			cmd: "set city paris xx", | ||||||
| 			cmd:  command.MustParse[*str.Set]("set city paris xx"), | 			res: false, | ||||||
| 			res:  false, | 			out: "(nil)", | ||||||
| 			out:  "(nil)", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set ex", | 			cmd: "set name alice ex 10", | ||||||
| 			cmd:  command.MustParse[*str.Set]("set name alice ex 10"), | 			res: true, | ||||||
| 			res:  true, | 			out: "OK", | ||||||
| 			out:  "OK", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set keepttl", | 			cmd: "set name alice keepttl", | ||||||
| 			cmd:  command.MustParse[*str.Set]("set name alice keepttl"), | 			res: true, | ||||||
| 			res:  true, | 			out: "OK", | ||||||
| 			out:  "OK", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set nx ex", | 			cmd: "set color blue nx ex 10", | ||||||
| 			cmd:  command.MustParse[*str.Set]("set color blue nx ex 10"), | 			res: true, | ||||||
| 			res:  true, | 			out: "OK", | ||||||
| 			out:  "OK", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set get", | 			cmd: "set name bob get", | ||||||
| 			cmd:  command.MustParse[*str.Set]("set name bob get"), | 			res: core.Value("alice"), | ||||||
| 			res:  core.Value("alice"), | 			out: "alice", | ||||||
| 			out:  "alice", |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "set get nil", | 			cmd: "set country france get", | ||||||
| 			cmd:  command.MustParse[*str.Set]("set country france get"), | 			res: core.Value(nil), | ||||||
| 			res:  core.Value(nil), | 			out: "(nil)", | ||||||
| 			out:  "(nil)", |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := test.cmd.Run(conn, red) | 			cmd := redis.MustParse(ParseSet, test.cmd) | ||||||
|  | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| 			testx.AssertEqual(t, res, test.res) | 			testx.AssertEqual(t, res, test.res) | ||||||
| 			testx.AssertEqual(t, conn.Out(), test.out) | 			testx.AssertEqual(t, conn.Out(), test.out) | ||||||
|   | |||||||
| @@ -13,28 +13,28 @@ import ( | |||||||
| // https://redis.io/commands/setex | // https://redis.io/commands/setex | ||||||
| type SetEX struct { | type SetEX struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Value []byte | 	value []byte | ||||||
| 	TTL   time.Duration | 	ttl   time.Duration | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseSetEX(b redis.BaseCmd, multi int) (*SetEX, error) { | func ParseSetEX(b redis.BaseCmd, multi int) (*SetEX, error) { | ||||||
| 	cmd := &SetEX{BaseCmd: b} | 	cmd := &SetEX{BaseCmd: b} | ||||||
| 	var ttl int | 	var ttl int | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Int(&ttl), | 		parser.Int(&ttl), | ||||||
| 		parser.Bytes(&cmd.Value), | 		parser.Bytes(&cmd.value), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	cmd.TTL = time.Duration(multi*ttl) * time.Millisecond | 	cmd.ttl = time.Duration(multi*ttl) * time.Millisecond | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *SetEX) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *SetEX) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	err := red.Str().SetExpires(cmd.Key, cmd.Value, cmd.TTL) | 	err := red.Str().SetExpires(cmd.key, cmd.value, cmd.ttl) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,74 +1,73 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestSetEXParse(t *testing.T) { | func TestSetEXParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want SetEX | ||||||
| 		want str.SetEX |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "setex", | 			cmd:  "setex", | ||||||
| 			args: command.BuildArgs("setex"), | 			want: SetEX{}, | ||||||
| 			want: str.SetEX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "setex name", | 			cmd:  "setex name", | ||||||
| 			args: command.BuildArgs("setex", "name"), | 			want: SetEX{}, | ||||||
| 			want: str.SetEX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "setex name alice", | 			cmd:  "setex name alice", | ||||||
| 			args: command.BuildArgs("setex", "name", "alice"), | 			want: SetEX{}, | ||||||
| 			want: str.SetEX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "setex name alice 60", | 			cmd:  "setex name alice 60", | ||||||
| 			args: command.BuildArgs("setex", "name", "alice", "60"), | 			want: SetEX{}, | ||||||
| 			want: str.SetEX{}, |  | ||||||
| 			err:  redis.ErrInvalidInt, | 			err:  redis.ErrInvalidInt, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "setex name 60 alice", | 			cmd:  "setex name 60 alice", | ||||||
| 			args: command.BuildArgs("setex", "name", "60", "alice"), | 			want: SetEX{key: "name", value: []byte("alice"), ttl: 60 * 1000 * time.Millisecond}, | ||||||
| 			want: str.SetEX{Key: "name", Value: []byte("alice"), TTL: 60 * 1000 * time.Millisecond}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	parse := func(b redis.BaseCmd) (*SetEX, error) { | ||||||
|  | 		return ParseSetEX(b, 1000) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(parse, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.SetEX) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.value, test.want.value) | ||||||
| 				testx.AssertEqual(t, cm.Value, test.want.Value) | 				testx.AssertEqual(t, cmd.ttl, test.want.ttl) | ||||||
| 				testx.AssertEqual(t, cm.TTL, test.want.TTL) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestSetEXExec(t *testing.T) { | func TestSetEXExec(t *testing.T) { | ||||||
|  | 	parse := func(b redis.BaseCmd) (*SetEX, error) { | ||||||
|  | 		return ParseSetEX(b, 1000) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	t.Run("create", func(t *testing.T) { | 	t.Run("create", func(t *testing.T) { | ||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.SetEX]("setex name 60 alice") | 		cmd := redis.MustParse(parse, "setex name 60 alice") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -89,7 +88,7 @@ func TestSetEXExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.SetEX]("setex name 60 bob") | 		cmd := redis.MustParse(parse, "setex name 60 bob") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -110,7 +109,7 @@ func TestSetEXExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().SetExpires("name", "alice", 60*time.Second) | 		_ = db.Str().SetExpires("name", "alice", 60*time.Second) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.SetEX]("setex name 10 bob") | 		cmd := redis.MustParse(parse, "setex name 10 bob") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -7,8 +7,8 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/setnx | // https://redis.io/commands/setnx | ||||||
| type SetNX struct { | type SetNX struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Value []byte | 	value []byte | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseSetNX(b redis.BaseCmd) (*SetNX, error) { | func ParseSetNX(b redis.BaseCmd) (*SetNX, error) { | ||||||
| @@ -16,13 +16,13 @@ func ParseSetNX(b redis.BaseCmd) (*SetNX, error) { | |||||||
| 	if len(cmd.Args()) != 2 { | 	if len(cmd.Args()) != 2 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	cmd.Value = cmd.Args()[1] | 	cmd.value = cmd.Args()[1] | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *SetNX) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *SetNX) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	out, err := red.Str().SetWith(cmd.Key, cmd.Value).IfNotExists().Run() | 	out, err := red.Str().SetWith(cmd.key, cmd.value).IfNotExists().Run() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,55 +1,47 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	str "github.com/nalgeon/redka/internal/command/string" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestSetNXParse(t *testing.T) { | func TestSetNXParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want SetNX | ||||||
| 		want str.SetNX |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "setnx", | 			cmd:  "setnx", | ||||||
| 			args: command.BuildArgs("setnx"), | 			want: SetNX{}, | ||||||
| 			want: str.SetNX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "setnx name", | 			cmd:  "setnx name", | ||||||
| 			args: command.BuildArgs("setnx", "name"), | 			want: SetNX{}, | ||||||
| 			want: str.SetNX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "setnx name alice", | 			cmd:  "setnx name alice", | ||||||
| 			args: command.BuildArgs("setnx", "name", "alice"), | 			want: SetNX{key: "name", value: []byte("alice")}, | ||||||
| 			want: str.SetNX{Key: "name", Value: []byte("alice")}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "setnx name alice 60", | 			cmd:  "setnx name alice 60", | ||||||
| 			args: command.BuildArgs("setnx", "name", "alice", "60"), | 			want: SetNX{}, | ||||||
| 			want: str.SetNX{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseSetNX, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*str.SetNX) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.value, test.want.value) | ||||||
| 				testx.AssertEqual(t, cm.Value, test.want.Value) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -60,7 +52,7 @@ func TestSetNXExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.SetNX]("setnx name alice") | 		cmd := redis.MustParse(ParseSetNX, "setnx name alice") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -77,7 +69,7 @@ func TestSetNXExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_ = db.Str().Set("name", "alice") | 		_ = db.Str().Set("name", "alice") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*str.SetNX]("setnx name bob") | 		cmd := redis.MustParse(ParseSetNX, "setnx name bob") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| package string_test | package string | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|   | |||||||
| @@ -11,15 +11,15 @@ import ( | |||||||
| // https://redis.io/commands/zadd | // https://redis.io/commands/zadd | ||||||
| type ZAdd struct { | type ZAdd struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key   string | 	key   string | ||||||
| 	Items map[any]float64 | 	items map[any]float64 | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseZAdd(b redis.BaseCmd) (*ZAdd, error) { | func ParseZAdd(b redis.BaseCmd) (*ZAdd, error) { | ||||||
| 	cmd := &ZAdd{BaseCmd: b} | 	cmd := &ZAdd{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.FloatMap(&cmd.Items), | 		parser.FloatMap(&cmd.items), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -28,7 +28,7 @@ func ParseZAdd(b redis.BaseCmd) (*ZAdd, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ZAdd) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ZAdd) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	count, err := red.ZSet().AddMany(cmd.Key, cmd.Items) | 	count, err := red.ZSet().AddMany(cmd.key, cmd.items) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,55 +1,46 @@ | |||||||
| package zset_test | package zset | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/zset" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestZAddParse(t *testing.T) { | func TestZAddParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want ZAdd | ||||||
| 		want zset.ZAdd |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "zadd", | 			cmd:  "zadd", | ||||||
| 			args: command.BuildArgs("zadd"), | 			want: ZAdd{}, | ||||||
| 			want: zset.ZAdd{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zadd key", | 			cmd:  "zadd key", | ||||||
| 			args: command.BuildArgs("zadd", "key"), | 			want: ZAdd{}, | ||||||
| 			want: zset.ZAdd{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zadd key one", | 			cmd:  "zadd key one", | ||||||
| 			args: command.BuildArgs("zadd", "key", "one"), | 			want: ZAdd{}, | ||||||
| 			want: zset.ZAdd{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zadd key 1 one", | 			cmd:  "zadd key 1.1 one", | ||||||
| 			args: command.BuildArgs("zadd", "key", "1.1", "one"), | 			want: ZAdd{key: "key", items: map[any]float64{"one": 1.1}}, | ||||||
| 			want: zset.ZAdd{Key: "key", Items: map[any]float64{"one": 1.1}}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zadd key 1 one 2", | 			cmd:  "zadd key 1.1 one 2.2", | ||||||
| 			args: command.BuildArgs("zadd", "key", "1.1", "one", "2.2"), | 			want: ZAdd{}, | ||||||
| 			want: zset.ZAdd{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zadd key one 1.1 two 2.2", | 			cmd: "zadd key 1.1 one 2.2 two", | ||||||
| 			args: command.BuildArgs("zadd", "key", "1.1", "one", "2.2", "two"), | 			want: ZAdd{key: "key", items: map[any]float64{ | ||||||
| 			want: zset.ZAdd{Key: "key", Items: map[any]float64{ |  | ||||||
| 				"one": 1.1, | 				"one": 1.1, | ||||||
| 				"two": 2.2, | 				"two": 2.2, | ||||||
| 			}}, | 			}}, | ||||||
| @@ -58,13 +49,12 @@ func TestZAddParse(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseZAdd, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*zset.ZAdd) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.items, test.want.items) | ||||||
| 				testx.AssertEqual(t, cm.Items, test.want.Items) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -75,7 +65,7 @@ func TestZAddExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZAdd]("zadd key 11 one") | 		cmd := redis.MustParse(ParseZAdd, "zadd key 11 one") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -89,7 +79,7 @@ func TestZAddExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZAdd]("zadd key 11 one 22 two") | 		cmd := redis.MustParse(ParseZAdd, "zadd key 11 one 22 two") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -108,7 +98,7 @@ func TestZAddExec(t *testing.T) { | |||||||
|  |  | ||||||
| 		_, _ = db.ZSet().Add("key", "one", 11) | 		_, _ = db.ZSet().Add("key", "one", 11) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZAdd]("zadd key 12 one 22 two") | 		cmd := redis.MustParse(ParseZAdd, "zadd key 12 one 22 two") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -127,7 +117,7 @@ func TestZAddExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "one", 11) | 		_, _ = db.ZSet().Add("key", "one", 11) | ||||||
| 		_, _ = db.ZSet().Add("key", "two", 22) | 		_, _ = db.ZSet().Add("key", "two", 22) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZAdd]("zadd key 12 one 23 two") | 		cmd := redis.MustParse(ParseZAdd, "zadd key 12 one 23 two") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -144,7 +134,7 @@ func TestZAddExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("key", "value") | 		_ = db.Str().Set("key", "value") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZAdd]("zadd key 11 one") | 		cmd := redis.MustParse(ParseZAdd, "zadd key 11 one") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import "github.com/nalgeon/redka/internal/redis" | |||||||
| // https://redis.io/commands/zcard | // https://redis.io/commands/zcard | ||||||
| type ZCard struct { | type ZCard struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key string | 	key string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseZCard(b redis.BaseCmd) (*ZCard, error) { | func ParseZCard(b redis.BaseCmd) (*ZCard, error) { | ||||||
| @@ -15,12 +15,12 @@ func ParseZCard(b redis.BaseCmd) (*ZCard, error) { | |||||||
| 	if len(cmd.Args()) != 1 { | 	if len(cmd.Args()) != 1 { | ||||||
| 		return cmd, redis.ErrInvalidArgNum | 		return cmd, redis.ErrInvalidArgNum | ||||||
| 	} | 	} | ||||||
| 	cmd.Key = string(cmd.Args()[0]) | 	cmd.key = string(cmd.Args()[0]) | ||||||
| 	return cmd, nil | 	return cmd, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ZCard) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ZCard) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	n, err := red.ZSet().Len(cmd.Key) | 	n, err := red.ZSet().Len(cmd.key) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,48 +1,41 @@ | |||||||
| package zset_test | package zset | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/zset" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestZCardParse(t *testing.T) { | func TestZCardParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want ZCard | ||||||
| 		want zset.ZCard |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "zcard", | 			cmd:  "zcard", | ||||||
| 			args: command.BuildArgs("zcard"), | 			want: ZCard{}, | ||||||
| 			want: zset.ZCard{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zcard key", | 			cmd:  "zcard key", | ||||||
| 			args: command.BuildArgs("zcard", "key"), | 			want: ZCard{key: "key"}, | ||||||
| 			want: zset.ZCard{Key: "key"}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zcard key one", | 			cmd:  "zcard key one", | ||||||
| 			args: command.BuildArgs("zcard", "key", "one"), | 			want: ZCard{}, | ||||||
| 			want: zset.ZCard{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseZCard, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*zset.ZCard) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -55,7 +48,7 @@ func TestZCardExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "one", 11) | 		_, _ = db.ZSet().Add("key", "one", 11) | ||||||
| 		_, _ = db.ZSet().Add("key", "two", 22) | 		_, _ = db.ZSet().Add("key", "two", 22) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZCard]("zcard key") | 		cmd := redis.MustParse(ParseZCard, "zcard key") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -68,7 +61,7 @@ func TestZCardExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "one", 11) | 		_, _ = db.ZSet().Add("key", "one", 11) | ||||||
| 		_, _ = db.ZSet().Delete("key", "one") | 		_, _ = db.ZSet().Delete("key", "one") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZCard]("zcard key") | 		cmd := redis.MustParse(ParseZCard, "zcard key") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -79,7 +72,7 @@ func TestZCardExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZCard]("zcard key") | 		cmd := redis.MustParse(ParseZCard, "zcard key") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -91,7 +84,7 @@ func TestZCardExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("key", "value") | 		_ = db.Str().Set("key", "value") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZCard]("zcard key") | 		cmd := redis.MustParse(ParseZCard, "zcard key") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -10,17 +10,17 @@ import ( | |||||||
| // https://redis.io/commands/zcount | // https://redis.io/commands/zcount | ||||||
| type ZCount struct { | type ZCount struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key string | 	key string | ||||||
| 	Min float64 | 	min float64 | ||||||
| 	Max float64 | 	max float64 | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseZCount(b redis.BaseCmd) (*ZCount, error) { | func ParseZCount(b redis.BaseCmd) (*ZCount, error) { | ||||||
| 	cmd := &ZCount{BaseCmd: b} | 	cmd := &ZCount{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Float(&cmd.Min), | 		parser.Float(&cmd.min), | ||||||
| 		parser.Float(&cmd.Max), | 		parser.Float(&cmd.max), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -29,7 +29,7 @@ func ParseZCount(b redis.BaseCmd) (*ZCount, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ZCount) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ZCount) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	n, err := red.ZSet().Count(cmd.Key, cmd.Min, cmd.Max) | 	n, err := red.ZSet().Count(cmd.key, cmd.min, cmd.max) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,62 +1,53 @@ | |||||||
| package zset_test | package zset | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/zset" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestZCountParse(t *testing.T) { | func TestZCountParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want ZCount | ||||||
| 		want zset.ZCount |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "zcount", | 			cmd:  "zcount", | ||||||
| 			args: command.BuildArgs("zcount"), | 			want: ZCount{}, | ||||||
| 			want: zset.ZCount{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zcount key", | 			cmd:  "zcount key", | ||||||
| 			args: command.BuildArgs("zcount", "key"), | 			want: ZCount{}, | ||||||
| 			want: zset.ZCount{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zcount key 11", | 			cmd:  "zcount key 1.1", | ||||||
| 			args: command.BuildArgs("zcount", "key", "11"), | 			want: ZCount{}, | ||||||
| 			want: zset.ZCount{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zcount key 11 22", | 			cmd:  "zcount key 1.1 2.2", | ||||||
| 			args: command.BuildArgs("zcount", "key", "1.1", "2.2"), | 			want: ZCount{key: "key", min: 1.1, max: 2.2}, | ||||||
| 			want: zset.ZCount{Key: "key", Min: 1.1, Max: 2.2}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zcount key 11 22 33", | 			cmd:  "zcount key 1.1 2.2 3.3", | ||||||
| 			args: command.BuildArgs("zcount", "key", "1.1", "2.2", "3.3"), | 			want: ZCount{}, | ||||||
| 			want: zset.ZCount{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseZCount, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*zset.ZCount) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.min, test.want.min) | ||||||
| 				testx.AssertEqual(t, cm.Min, test.want.Min) | 				testx.AssertEqual(t, cmd.max, test.want.max) | ||||||
| 				testx.AssertEqual(t, cm.Max, test.want.Max) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -70,7 +61,7 @@ func TestZCountExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "two", 22) | 		_, _ = db.ZSet().Add("key", "two", 22) | ||||||
| 		_, _ = db.ZSet().Add("key", "thr", 33) | 		_, _ = db.ZSet().Add("key", "thr", 33) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZCount]("zcount key 15 25") | 		cmd := redis.MustParse(ParseZCount, "zcount key 15 25") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -84,7 +75,7 @@ func TestZCountExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "two", 22) | 		_, _ = db.ZSet().Add("key", "two", 22) | ||||||
| 		_, _ = db.ZSet().Add("key", "thr", 33) | 		_, _ = db.ZSet().Add("key", "thr", 33) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZCount]("zcount key 11 33") | 		cmd := redis.MustParse(ParseZCount, "zcount key 11 33") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -98,7 +89,7 @@ func TestZCountExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "two", 22) | 		_, _ = db.ZSet().Add("key", "two", 22) | ||||||
| 		_, _ = db.ZSet().Add("key", "thr", 33) | 		_, _ = db.ZSet().Add("key", "thr", 33) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZCount]("zcount key 44 55") | 		cmd := redis.MustParse(ParseZCount, "zcount key 44 55") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -109,7 +100,7 @@ func TestZCountExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZCount]("zcount key 11 33") | 		cmd := redis.MustParse(ParseZCount, "zcount key 11 33") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -121,7 +112,7 @@ func TestZCountExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("key", "value") | 		_ = db.Str().Set("key", "value") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZCount]("zcount key 11 33") | 		cmd := redis.MustParse(ParseZCount, "zcount key 11 33") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -10,17 +10,17 @@ import ( | |||||||
| // https://redis.io/commands/zincrby | // https://redis.io/commands/zincrby | ||||||
| type ZIncrBy struct { | type ZIncrBy struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key    string | 	key    string | ||||||
| 	Delta  float64 | 	delta  float64 | ||||||
| 	Member string | 	member string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseZIncrBy(b redis.BaseCmd) (*ZIncrBy, error) { | func ParseZIncrBy(b redis.BaseCmd) (*ZIncrBy, error) { | ||||||
| 	cmd := &ZIncrBy{BaseCmd: b} | 	cmd := &ZIncrBy{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Float(&cmd.Delta), | 		parser.Float(&cmd.delta), | ||||||
| 		parser.String(&cmd.Member), | 		parser.String(&cmd.member), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -29,7 +29,7 @@ func ParseZIncrBy(b redis.BaseCmd) (*ZIncrBy, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ZIncrBy) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ZIncrBy) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	score, err := red.ZSet().Incr(cmd.Key, cmd.Member, cmd.Delta) | 	score, err := red.ZSet().Incr(cmd.key, cmd.member, cmd.delta) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
| @@ -1,56 +1,48 @@ | |||||||
| package zset_test | package zset | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/zset" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestZIncrByParse(t *testing.T) { | func TestZIncrByParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want ZIncrBy | ||||||
| 		want zset.ZIncrBy |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "zincrby", | 			cmd:  "zincrby", | ||||||
| 			args: command.BuildArgs("zincrby"), | 			want: ZIncrBy{}, | ||||||
| 			want: zset.ZIncrBy{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zincrby key", | 			cmd:  "zincrby key", | ||||||
| 			args: command.BuildArgs("zincrby", "key"), | 			want: ZIncrBy{}, | ||||||
| 			want: zset.ZIncrBy{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zincrby key one", | 			cmd:  "zincrby key one", | ||||||
| 			args: command.BuildArgs("zincrby", "key", "one"), | 			want: ZIncrBy{}, | ||||||
| 			want: zset.ZIncrBy{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zincrby key 11 one", | 			cmd:  "zincrby key 11 one", | ||||||
| 			args: command.BuildArgs("zincrby", "key", "11", "one"), | 			want: ZIncrBy{key: "key", member: "one", delta: 11.0}, | ||||||
| 			want: zset.ZIncrBy{Key: "key", Member: "one", Delta: 11.0}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseZIncrBy, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*zset.ZIncrBy) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.member, test.want.member) | ||||||
| 				testx.AssertEqual(t, cm.Member, test.want.Member) | 				testx.AssertEqual(t, cmd.delta, test.want.delta) | ||||||
| 				testx.AssertEqual(t, cm.Delta, test.want.Delta) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -61,7 +53,7 @@ func TestZIncrByExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZIncrBy]("zincrby key 25.5 one") | 		cmd := redis.MustParse(ParseZIncrBy, "zincrby key 25.5 one") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -76,7 +68,7 @@ func TestZIncrByExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_, _ = db.ZSet().Add("key", "one", 10) | 		_, _ = db.ZSet().Add("key", "one", 10) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZIncrBy]("zincrby key 25.5 two") | 		cmd := redis.MustParse(ParseZIncrBy, "zincrby key 25.5 two") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -91,7 +83,7 @@ func TestZIncrByExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_, _ = db.ZSet().Add("key", "one", 25.5) | 		_, _ = db.ZSet().Add("key", "one", 25.5) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZIncrBy]("zincrby key 10.5 one") | 		cmd := redis.MustParse(ParseZIncrBy, "zincrby key 10.5 one") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -106,7 +98,7 @@ func TestZIncrByExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_, _ = db.ZSet().Add("key", "one", 25.5) | 		_, _ = db.ZSet().Add("key", "one", 25.5) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZIncrBy]("zincrby key -10.5 one") | 		cmd := redis.MustParse(ParseZIncrBy, "zincrby key -10.5 one") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -121,7 +113,7 @@ func TestZIncrByExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_ = red.Str().Set("key", "one") | 		_ = red.Str().Set("key", "one") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZIncrBy]("zincrby key 25.5 one") | 		cmd := redis.MustParse(ParseZIncrBy, "zincrby key 25.5 one") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -11,9 +11,9 @@ import ( | |||||||
| // https://redis.io/commands/zinter | // https://redis.io/commands/zinter | ||||||
| type ZInter struct { | type ZInter struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Keys       []string | 	keys       []string | ||||||
| 	Aggregate  string | 	aggregate  string | ||||||
| 	WithScores bool | 	withScores bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseZInter(b redis.BaseCmd) (*ZInter, error) { | func ParseZInter(b redis.BaseCmd) (*ZInter, error) { | ||||||
| @@ -21,9 +21,9 @@ func ParseZInter(b redis.BaseCmd) (*ZInter, error) { | |||||||
| 	var nKeys int | 	var nKeys int | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.Int(&nKeys), | 		parser.Int(&nKeys), | ||||||
| 		parser.StringsN(&cmd.Keys, &nKeys), | 		parser.StringsN(&cmd.keys, &nKeys), | ||||||
| 		parser.Named("aggregate", parser.Enum(&cmd.Aggregate, sqlx.Sum, sqlx.Min, sqlx.Max)), | 		parser.Named("aggregate", parser.Enum(&cmd.aggregate, sqlx.Sum, sqlx.Min, sqlx.Max)), | ||||||
| 		parser.Flag("withscores", &cmd.WithScores), | 		parser.Flag("withscores", &cmd.withScores), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -32,8 +32,8 @@ func ParseZInter(b redis.BaseCmd) (*ZInter, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ZInter) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ZInter) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	inter := red.ZSet().InterWith(cmd.Keys...) | 	inter := red.ZSet().InterWith(cmd.keys...) | ||||||
| 	switch cmd.Aggregate { | 	switch cmd.aggregate { | ||||||
| 	case sqlx.Min: | 	case sqlx.Min: | ||||||
| 		inter = inter.Min() | 		inter = inter.Min() | ||||||
| 	case sqlx.Max: | 	case sqlx.Max: | ||||||
| @@ -48,7 +48,7 @@ func (cmd *ZInter) Run(w redis.Writer, red redis.Redka) (any, error) { | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if cmd.WithScores { | 	if cmd.withScores { | ||||||
| 		w.WriteArray(len(items) * 2) | 		w.WriteArray(len(items) * 2) | ||||||
| 		for _, item := range items { | 		for _, item := range items { | ||||||
| 			w.WriteBulk(item.Elem) | 			w.WriteBulk(item.Elem) | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package zset_test | package zset | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/zset" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/rzset" | 	"github.com/nalgeon/redka/internal/rzset" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,82 +10,70 @@ import ( | |||||||
|  |  | ||||||
| func TestZInterParse(t *testing.T) { | func TestZInterParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want ZInter | ||||||
| 		want zset.ZInter |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinter", | 			cmd:  "zinter", | ||||||
| 			args: command.BuildArgs("zinter"), | 			want: ZInter{}, | ||||||
| 			want: zset.ZInter{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinter 1", | 			cmd:  "zinter 1", | ||||||
| 			args: command.BuildArgs("zinter", "1"), | 			want: ZInter{}, | ||||||
| 			want: zset.ZInter{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinter 1 key", | 			cmd:  "zinter 1 key", | ||||||
| 			args: command.BuildArgs("zinter", "1", "key"), | 			want: ZInter{keys: []string{"key"}}, | ||||||
| 			want: zset.ZInter{Keys: []string{"key"}}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinter 2 k1 k2", | 			cmd:  "zinter 2 k1 k2", | ||||||
| 			args: command.BuildArgs("zinter", "2", "k1", "k2"), | 			want: ZInter{keys: []string{"k1", "k2"}}, | ||||||
| 			want: zset.ZInter{Keys: []string{"k1", "k2"}}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinter 1 k1 k2", | 			cmd:  "zinter 1 k1 k2", | ||||||
| 			args: command.BuildArgs("zinter", "1", "k1", "k2"), | 			want: ZInter{}, | ||||||
| 			want: zset.ZInter{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinter 2 k1 k2 min", | 			cmd:  "zinter 2 k1 k2 min", | ||||||
| 			args: command.BuildArgs("zinter", "2", "k1", "k2", "min"), | 			want: ZInter{}, | ||||||
| 			want: zset.ZInter{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinter 2 k1 k2 aggregate min", | 			cmd:  "zinter 2 k1 k2 aggregate min", | ||||||
| 			args: command.BuildArgs("zinter", "2", "k1", "k2", "aggregate", "min"), | 			want: ZInter{keys: []string{"k1", "k2"}, aggregate: "min"}, | ||||||
| 			want: zset.ZInter{Keys: []string{"k1", "k2"}, Aggregate: "min"}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinter 2 k1 k2 aggregate avg", | 			cmd:  "zinter 2 k1 k2 aggregate avg", | ||||||
| 			args: command.BuildArgs("zinter", "2", "k1", "k2", "aggregate", "avg"), | 			want: ZInter{}, | ||||||
| 			want: zset.ZInter{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinter 2 k1 k2 withscores", | 			cmd:  "zinter 2 k1 k2 withscores", | ||||||
| 			args: command.BuildArgs("zinter", "2", "k1", "k2", "withscores"), | 			want: ZInter{keys: []string{"k1", "k2"}, withScores: true}, | ||||||
| 			want: zset.ZInter{Keys: []string{"k1", "k2"}, WithScores: true}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinter 3 k1 k2 k3 withscores aggregate sum", | 			cmd:  "zinter 3 k1 k2 k3 withscores aggregate sum", | ||||||
| 			args: command.BuildArgs("zinter", "3", "k1", "k2", "k3", "withscores", "aggregate", "sum"), | 			want: ZInter{keys: []string{"k1", "k2", "k3"}, aggregate: "sum", withScores: true}, | ||||||
| 			want: zset.ZInter{Keys: []string{"k1", "k2", "k3"}, Aggregate: "sum", WithScores: true}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseZInter, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*zset.ZInter) | 				testx.AssertEqual(t, cmd.keys, test.want.keys) | ||||||
| 				testx.AssertEqual(t, cm.Keys, test.want.Keys) | 				testx.AssertEqual(t, cmd.aggregate, test.want.aggregate) | ||||||
| 				testx.AssertEqual(t, cm.Aggregate, test.want.Aggregate) | 				testx.AssertEqual(t, cmd.withScores, test.want.withScores) | ||||||
| 				testx.AssertEqual(t, cm.WithScores, test.want.WithScores) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -114,7 +100,7 @@ func TestZInterExec(t *testing.T) { | |||||||
| 			"fou": 400, | 			"fou": 400, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInter]("zinter 3 key1 key2 key3") | 		cmd := redis.MustParse(ParseZInter, "zinter 3 key1 key2 key3") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -141,7 +127,7 @@ func TestZInterExec(t *testing.T) { | |||||||
| 			"fou": 400, | 			"fou": 400, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInter]("zinter 3 key1 key2 key3 withscores") | 		cmd := redis.MustParse(ParseZInter, "zinter 3 key1 key2 key3 withscores") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -168,7 +154,7 @@ func TestZInterExec(t *testing.T) { | |||||||
| 			"fou": 400, | 			"fou": 400, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInter]("zinter 3 key1 key2 key3 aggregate min withscores") | 		cmd := redis.MustParse(ParseZInter, "zinter 3 key1 key2 key3 aggregate min withscores") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -184,7 +170,7 @@ func TestZInterExec(t *testing.T) { | |||||||
| 			"thr": 3, | 			"thr": 3, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInter]("zinter 1 key1") | 		cmd := redis.MustParse(ParseZInter, "zinter 1 key1") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -198,7 +184,7 @@ func TestZInterExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key2", "two", 1) | 		_, _ = db.ZSet().Add("key2", "two", 1) | ||||||
| 		_, _ = db.ZSet().Add("key3", "thr", 1) | 		_, _ = db.ZSet().Add("key3", "thr", 1) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInter]("zinter 3 key1 key2 key3") | 		cmd := redis.MustParse(ParseZInter, "zinter 3 key1 key2 key3") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -209,7 +195,7 @@ func TestZInterExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInter]("zinter 1 key") | 		cmd := redis.MustParse(ParseZInter, "zinter 1 key") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -221,7 +207,7 @@ func TestZInterExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("key", "value") | 		_ = db.Str().Set("key", "value") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInter]("zinter 1 key") | 		cmd := redis.MustParse(ParseZInter, "zinter 1 key") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -11,19 +11,19 @@ import ( | |||||||
| // https://redis.io/commands/zinterstore | // https://redis.io/commands/zinterstore | ||||||
| type ZInterStore struct { | type ZInterStore struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Dest      string | 	dest      string | ||||||
| 	Keys      []string | 	keys      []string | ||||||
| 	Aggregate string | 	aggregate string | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseZInterStore(b redis.BaseCmd) (*ZInterStore, error) { | func ParseZInterStore(b redis.BaseCmd) (*ZInterStore, error) { | ||||||
| 	cmd := &ZInterStore{BaseCmd: b} | 	cmd := &ZInterStore{BaseCmd: b} | ||||||
| 	var nKeys int | 	var nKeys int | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Dest), | 		parser.String(&cmd.dest), | ||||||
| 		parser.Int(&nKeys), | 		parser.Int(&nKeys), | ||||||
| 		parser.StringsN(&cmd.Keys, &nKeys), | 		parser.StringsN(&cmd.keys, &nKeys), | ||||||
| 		parser.Named("aggregate", parser.Enum(&cmd.Aggregate, sqlx.Sum, sqlx.Min, sqlx.Max)), | 		parser.Named("aggregate", parser.Enum(&cmd.aggregate, sqlx.Sum, sqlx.Min, sqlx.Max)), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -32,8 +32,8 @@ func ParseZInterStore(b redis.BaseCmd) (*ZInterStore, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ZInterStore) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ZInterStore) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	inter := red.ZSet().InterWith(cmd.Keys...).Dest(cmd.Dest) | 	inter := red.ZSet().InterWith(cmd.keys...).Dest(cmd.dest) | ||||||
| 	switch cmd.Aggregate { | 	switch cmd.aggregate { | ||||||
| 	case sqlx.Min: | 	case sqlx.Min: | ||||||
| 		inter = inter.Min() | 		inter = inter.Min() | ||||||
| 	case sqlx.Max: | 	case sqlx.Max: | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package zset_test | package zset | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/zset" |  | ||||||
| 	"github.com/nalgeon/redka/internal/core" | 	"github.com/nalgeon/redka/internal/core" | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,76 +10,65 @@ import ( | |||||||
|  |  | ||||||
| func TestZInterStoreParse(t *testing.T) { | func TestZInterStoreParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want ZInterStore | ||||||
| 		want zset.ZInterStore |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinterstore", | 			cmd:  "zinterstore", | ||||||
| 			args: command.BuildArgs("zinterstore"), | 			want: ZInterStore{}, | ||||||
| 			want: zset.ZInterStore{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinterstore dest", | 			cmd:  "zinterstore dest", | ||||||
| 			args: command.BuildArgs("zinterstore", "dest"), | 			want: ZInterStore{}, | ||||||
| 			want: zset.ZInterStore{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinterstore dest 1", | 			cmd:  "zinterstore dest 1", | ||||||
| 			args: command.BuildArgs("zinterstore", "dest", "1"), | 			want: ZInterStore{}, | ||||||
| 			want: zset.ZInterStore{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinterstore dest 1 key", | 			cmd:  "zinterstore dest 1 key", | ||||||
| 			args: command.BuildArgs("zinterstore", "dest", "1", "key"), | 			want: ZInterStore{dest: "dest", keys: []string{"key"}}, | ||||||
| 			want: zset.ZInterStore{Dest: "dest", Keys: []string{"key"}}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinterstore dest 2 k1 k2", | 			cmd:  "zinterstore dest 2 k1 k2", | ||||||
| 			args: command.BuildArgs("zinterstore", "dest", "2", "k1", "k2"), | 			want: ZInterStore{dest: "dest", keys: []string{"k1", "k2"}}, | ||||||
| 			want: zset.ZInterStore{Dest: "dest", Keys: []string{"k1", "k2"}}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinterstore dest 1 k1 k2", | 			cmd:  "zinterstore dest 1 k1 k2", | ||||||
| 			args: command.BuildArgs("zinterstore", "dest", "1", "k1", "k2"), | 			want: ZInterStore{}, | ||||||
| 			want: zset.ZInterStore{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinterstore dest 2 k1 k2 min", | 			cmd:  "zinterstore dest 2 k1 k2 min", | ||||||
| 			args: command.BuildArgs("zinterstore", "dest", "2", "k1", "k2", "min"), | 			want: ZInterStore{}, | ||||||
| 			want: zset.ZInterStore{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinterstore dest 2 k1 k2 aggregate min", | 			cmd:  "zinterstore dest 2 k1 k2 aggregate min", | ||||||
| 			args: command.BuildArgs("zinterstore", "dest", "2", "k1", "k2", "aggregate", "min"), | 			want: ZInterStore{dest: "dest", keys: []string{"k1", "k2"}, aggregate: "min"}, | ||||||
| 			want: zset.ZInterStore{Dest: "dest", Keys: []string{"k1", "k2"}, Aggregate: "min"}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zinterstore dest 2 k1 k2 aggregate avg", | 			cmd:  "zinterstore dest 2 k1 k2 aggregate avg", | ||||||
| 			args: command.BuildArgs("zinterstore", "dest", "2", "k1", "k2", "aggregate", "avg"), | 			want: ZInterStore{}, | ||||||
| 			want: zset.ZInterStore{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseZInterStore, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*zset.ZInterStore) | 				testx.AssertEqual(t, cmd.dest, test.want.dest) | ||||||
| 				testx.AssertEqual(t, cm.Dest, test.want.Dest) | 				testx.AssertEqual(t, cmd.keys, test.want.keys) | ||||||
| 				testx.AssertEqual(t, cm.Keys, test.want.Keys) | 				testx.AssertEqual(t, cmd.aggregate, test.want.aggregate) | ||||||
| 				testx.AssertEqual(t, cm.Aggregate, test.want.Aggregate) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -108,7 +95,7 @@ func TestZInterStoreExec(t *testing.T) { | |||||||
| 			"fou": 400, | 			"fou": 400, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInterStore]("zinterstore dest 3 key1 key2 key3") | 		cmd := redis.MustParse(ParseZInterStore, "zinterstore dest 3 key1 key2 key3") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -139,7 +126,7 @@ func TestZInterStoreExec(t *testing.T) { | |||||||
| 		}) | 		}) | ||||||
| 		_, _ = db.ZSet().Add("dest", "one", 1) | 		_, _ = db.ZSet().Add("dest", "one", 1) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInterStore]("zinterstore dest 3 key1 key2 key3") | 		cmd := redis.MustParse(ParseZInterStore, "zinterstore dest 3 key1 key2 key3") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -171,7 +158,7 @@ func TestZInterStoreExec(t *testing.T) { | |||||||
| 			"fou": 400, | 			"fou": 400, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInterStore]("zinterstore dest 3 key1 key2 key3 aggregate min") | 		cmd := redis.MustParse(ParseZInterStore, "zinterstore dest 3 key1 key2 key3 aggregate min") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -192,7 +179,7 @@ func TestZInterStoreExec(t *testing.T) { | |||||||
| 			"thr": 3, | 			"thr": 3, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInterStore]("zinterstore dest 1 key1") | 		cmd := redis.MustParse(ParseZInterStore, "zinterstore dest 1 key1") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -209,7 +196,7 @@ func TestZInterStoreExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key2", "two", 1) | 		_, _ = db.ZSet().Add("key2", "two", 1) | ||||||
| 		_, _ = db.ZSet().Add("key3", "thr", 1) | 		_, _ = db.ZSet().Add("key3", "thr", 1) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInterStore]("zinterstore dest 3 key1 key2 key3") | 		cmd := redis.MustParse(ParseZInterStore, "zinterstore dest 3 key1 key2 key3") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -225,7 +212,7 @@ func TestZInterStoreExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key1", "one", 1) | 		_, _ = db.ZSet().Add("key1", "one", 1) | ||||||
| 		_, _ = db.ZSet().Add("dest", "one", 1) | 		_, _ = db.ZSet().Add("dest", "one", 1) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInterStore]("zinterstore dest 2 key1 key2") | 		cmd := redis.MustParse(ParseZInterStore, "zinterstore dest 2 key1 key2") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -241,7 +228,7 @@ func TestZInterStoreExec(t *testing.T) { | |||||||
| 		_ = db.Str().Set("key", "value") | 		_ = db.Str().Set("key", "value") | ||||||
| 		_, _ = db.ZSet().Add("dest", "one", 1) | 		_, _ = db.ZSet().Add("dest", "one", 1) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInterStore]("zinterstore dest 1 key") | 		cmd := redis.MustParse(ParseZInterStore, "zinterstore dest 1 key") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -257,7 +244,7 @@ func TestZInterStoreExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "one", 1) | 		_, _ = db.ZSet().Add("key", "one", 1) | ||||||
| 		_ = db.Str().Set("dest", "value") | 		_ = db.Str().Set("dest", "value") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZInterStore]("zinterstore dest 1 key") | 		cmd := redis.MustParse(ParseZInterStore, "zinterstore dest 1 key") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -10,26 +10,26 @@ import ( | |||||||
| // https://redis.io/commands/zrange | // https://redis.io/commands/zrange | ||||||
| type ZRange struct { | type ZRange struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key        string | 	key        string | ||||||
| 	Start      float64 | 	start      float64 | ||||||
| 	Stop       float64 | 	stop       float64 | ||||||
| 	ByScore    bool | 	byScore    bool | ||||||
| 	Rev        bool | 	rev        bool | ||||||
| 	Offset     int | 	offset     int | ||||||
| 	Count      int | 	count      int | ||||||
| 	WithScores bool | 	withScores bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseZRange(b redis.BaseCmd) (*ZRange, error) { | func ParseZRange(b redis.BaseCmd) (*ZRange, error) { | ||||||
| 	cmd := &ZRange{BaseCmd: b} | 	cmd := &ZRange{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Float(&cmd.Start), | 		parser.Float(&cmd.start), | ||||||
| 		parser.Float(&cmd.Stop), | 		parser.Float(&cmd.stop), | ||||||
| 		parser.Flag("byscore", &cmd.ByScore), | 		parser.Flag("byscore", &cmd.byScore), | ||||||
| 		parser.Flag("rev", &cmd.Rev), | 		parser.Flag("rev", &cmd.rev), | ||||||
| 		parser.Named("limit", parser.Int(&cmd.Offset), parser.Int(&cmd.Count)), | 		parser.Named("limit", parser.Int(&cmd.offset), parser.Int(&cmd.count)), | ||||||
| 		parser.Flag("withscores", &cmd.WithScores), | 		parser.Flag("withscores", &cmd.withScores), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -38,26 +38,26 @@ func ParseZRange(b redis.BaseCmd) (*ZRange, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ZRange) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ZRange) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	rang := red.ZSet().RangeWith(cmd.Key) | 	rang := red.ZSet().RangeWith(cmd.key) | ||||||
|  |  | ||||||
| 	// filter by score or rank | 	// filter by score or rank | ||||||
| 	if cmd.ByScore { | 	if cmd.byScore { | ||||||
| 		rang = rang.ByScore(cmd.Start, cmd.Stop) | 		rang = rang.ByScore(cmd.start, cmd.stop) | ||||||
| 	} else { | 	} else { | ||||||
| 		rang = rang.ByRank(int(cmd.Start), int(cmd.Stop)) | 		rang = rang.ByRank(int(cmd.start), int(cmd.stop)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// sort direction | 	// sort direction | ||||||
| 	if cmd.Rev { | 	if cmd.rev { | ||||||
| 		rang = rang.Desc() | 		rang = rang.Desc() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// limit and offset | 	// limit and offset | ||||||
| 	if cmd.Offset > 0 { | 	if cmd.offset > 0 { | ||||||
| 		rang = rang.Offset(cmd.Offset) | 		rang = rang.Offset(cmd.offset) | ||||||
| 	} | 	} | ||||||
| 	if cmd.Count > 0 { | 	if cmd.count > 0 { | ||||||
| 		rang = rang.Count(cmd.Count) | 		rang = rang.Count(cmd.count) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// run the command | 	// run the command | ||||||
| @@ -68,7 +68,7 @@ func (cmd *ZRange) Run(w redis.Writer, red redis.Redka) (any, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// write the response with/without scores | 	// write the response with/without scores | ||||||
| 	if cmd.WithScores { | 	if cmd.withScores { | ||||||
| 		w.WriteArray(len(items) * 2) | 		w.WriteArray(len(items) * 2) | ||||||
| 		for _, item := range items { | 		for _, item := range items { | ||||||
| 			w.WriteBulk(item.Elem) | 			w.WriteBulk(item.Elem) | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package zset_test | package zset | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/zset" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/rzset" | 	"github.com/nalgeon/redka/internal/rzset" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,95 +10,85 @@ import ( | |||||||
|  |  | ||||||
| func TestZRangeParse(t *testing.T) { | func TestZRangeParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want ZRange | ||||||
| 		want zset.ZRange |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange", | 			cmd:  "zrange", | ||||||
| 			args: command.BuildArgs("zrange"), | 			want: ZRange{}, | ||||||
| 			want: zset.ZRange{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange key", | 			cmd:  "zrange key", | ||||||
| 			args: command.BuildArgs("zrange", "key"), | 			want: ZRange{}, | ||||||
| 			want: zset.ZRange{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange key 11", | 			cmd:  "zrange key 11", | ||||||
| 			args: command.BuildArgs("zrange", "key", "11"), | 			want: ZRange{}, | ||||||
| 			want: zset.ZRange{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange key 11 22", | 			cmd:  "zrange key 11 22", | ||||||
| 			args: command.BuildArgs("zrange", "key", "11", "22"), | 			want: ZRange{key: "key", start: 11.0, stop: 22.0}, | ||||||
| 			want: zset.ZRange{Key: "key", Start: 11.0, Stop: 22.0}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange key 1.1 2.2 byscore", | 			cmd:  "zrange key 1.1 2.2 byscore", | ||||||
| 			args: command.BuildArgs("zrange", "key", "1.1", "2.2", "byscore"), | 			want: ZRange{key: "key", start: 1.1, stop: 2.2, byScore: true}, | ||||||
| 			want: zset.ZRange{Key: "key", Start: 1.1, Stop: 2.2, ByScore: true}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange key byscore exclusive", | 			cmd:  "zrange key (1 (2 byscore", | ||||||
| 			args: command.BuildArgs("zrange", "key", "(1", "(2", "byscore"), | 			want: ZRange{}, | ||||||
| 			want: zset.ZRange{}, |  | ||||||
| 			err:  redis.ErrInvalidFloat, | 			err:  redis.ErrInvalidFloat, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange key 11 22 rev", | 			cmd:  "zrange key 11 22 rev", | ||||||
| 			args: command.BuildArgs("zrange", "key", "11", "22", "rev"), | 			want: ZRange{key: "key", start: 11.0, stop: 22.0, rev: true}, | ||||||
| 			want: zset.ZRange{Key: "key", Start: 11.0, Stop: 22.0, Rev: true}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange key 11 22 byscore limit 10", | 			cmd:  "zrange key 11 22 byscore limit 10", | ||||||
| 			args: command.BuildArgs("zrange", "key", "11", "22", "byscore", "limit", "10"), | 			want: ZRange{}, | ||||||
| 			want: zset.ZRange{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange key 11 22 byscore limit 10 5", | 			cmd:  "zrange key 11 22 byscore limit 10 5", | ||||||
| 			args: command.BuildArgs("zrange", "key", "11", "22", "byscore", "limit", "10", "5"), | 			want: ZRange{key: "key", start: 11.0, stop: 22.0, byScore: true, offset: 10, count: 5}, | ||||||
| 			want: zset.ZRange{Key: "key", Start: 11.0, Stop: 22.0, ByScore: true, Offset: 10, Count: 5}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange key 11 22 withscores", | 			cmd:  "zrange key 11 22 withscores", | ||||||
| 			args: command.BuildArgs("zrange", "key", "11", "22", "withscores"), | 			want: ZRange{key: "key", start: 11.0, stop: 22.0, withScores: true}, | ||||||
| 			want: zset.ZRange{Key: "key", Start: 11.0, Stop: 22.0, WithScores: true}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrange key 11 22 limit 10 5 rev byscore withscores", | 			cmd: "zrange key 11 22 limit 10 5 rev byscore withscores", | ||||||
| 			args: command.BuildArgs("zrange", "key", "11", "22", "limit", "10", "5", | 			want: ZRange{ | ||||||
| 				"rev", "byscore", "withscores"), | 				key: "key", start: 11.0, stop: 22.0, | ||||||
| 			want: zset.ZRange{Key: "key", Start: 11.0, Stop: 22.0, ByScore: true, | 				byScore: true, rev: true, | ||||||
| 				Rev: true, Offset: 10, Count: 5, WithScores: true}, | 				offset: 10, count: 5, | ||||||
|  | 				withScores: true, | ||||||
|  | 			}, | ||||||
| 			err: nil, | 			err: nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseZRange, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*zset.ZRange) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.start, test.want.start) | ||||||
| 				testx.AssertEqual(t, cm.Start, test.want.Start) | 				testx.AssertEqual(t, cmd.stop, test.want.stop) | ||||||
| 				testx.AssertEqual(t, cm.Stop, test.want.Stop) | 				testx.AssertEqual(t, cmd.byScore, test.want.byScore) | ||||||
| 				testx.AssertEqual(t, cm.ByScore, test.want.ByScore) | 				testx.AssertEqual(t, cmd.rev, test.want.rev) | ||||||
| 				testx.AssertEqual(t, cm.Rev, test.want.Rev) | 				testx.AssertEqual(t, cmd.offset, test.want.offset) | ||||||
| 				testx.AssertEqual(t, cm.Offset, test.want.Offset) | 				testx.AssertEqual(t, cmd.count, test.want.count) | ||||||
| 				testx.AssertEqual(t, cm.Count, test.want.Count) | 				testx.AssertEqual(t, cmd.withScores, test.want.withScores) | ||||||
| 				testx.AssertEqual(t, cm.WithScores, test.want.WithScores) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -116,7 +104,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "2nd", 2) | 		_, _ = db.ZSet().Add("key", "2nd", 2) | ||||||
|  |  | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 1") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 1") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -124,7 +112,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "2,one,2nd") | 			testx.AssertEqual(t, conn.Out(), "2,one,2nd") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 5") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 5") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -132,7 +120,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "4,one,2nd,two,thr") | 			testx.AssertEqual(t, conn.Out(), "4,one,2nd,two,thr") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 3 5") | 			cmd := redis.MustParse(ParseZRange, "zrange key 3 5") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -140,7 +128,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "1,thr") | 			testx.AssertEqual(t, conn.Out(), "1,thr") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 4 5") | 			cmd := redis.MustParse(ParseZRange, "zrange key 4 5") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -157,7 +145,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "2nd", 2) | 		_, _ = db.ZSet().Add("key", "2nd", 2) | ||||||
|  |  | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 1 rev") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 1 rev") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -165,7 +153,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "2,thr,two") | 			testx.AssertEqual(t, conn.Out(), "2,thr,two") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 5 rev") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 5 rev") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -173,7 +161,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "4,thr,two,2nd,one") | 			testx.AssertEqual(t, conn.Out(), "4,thr,two,2nd,one") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 3 5 rev") | 			cmd := redis.MustParse(ParseZRange, "zrange key 3 5 rev") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -181,7 +169,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "1,one") | 			testx.AssertEqual(t, conn.Out(), "1,one") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 4 5 rev") | 			cmd := redis.MustParse(ParseZRange, "zrange key 4 5 rev") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -198,7 +186,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "2nd", 20) | 		_, _ = db.ZSet().Add("key", "2nd", 20) | ||||||
|  |  | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 10 byscore") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 10 byscore") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -206,7 +194,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "1,one") | 			testx.AssertEqual(t, conn.Out(), "1,one") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 50 byscore") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 50 byscore") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -214,7 +202,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "4,one,2nd,two,thr") | 			testx.AssertEqual(t, conn.Out(), "4,one,2nd,two,thr") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 30 50 byscore") | 			cmd := redis.MustParse(ParseZRange, "zrange key 30 50 byscore") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -222,7 +210,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "1,thr") | 			testx.AssertEqual(t, conn.Out(), "1,thr") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 40 50 byscore") | 			cmd := redis.MustParse(ParseZRange, "zrange key 40 50 byscore") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -239,7 +227,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "2nd", 20) | 		_, _ = db.ZSet().Add("key", "2nd", 20) | ||||||
|  |  | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 10 byscore rev") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 10 byscore rev") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -247,7 +235,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "1,one") | 			testx.AssertEqual(t, conn.Out(), "1,one") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 50 byscore rev") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 50 byscore rev") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -255,7 +243,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "4,thr,two,2nd,one") | 			testx.AssertEqual(t, conn.Out(), "4,thr,two,2nd,one") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 30 50 byscore rev") | 			cmd := redis.MustParse(ParseZRange, "zrange key 30 50 byscore rev") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -263,7 +251,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "1,thr") | 			testx.AssertEqual(t, conn.Out(), "1,thr") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 40 50 byscore rev") | 			cmd := redis.MustParse(ParseZRange, "zrange key 40 50 byscore rev") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -280,7 +268,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "2nd", 20) | 		_, _ = db.ZSet().Add("key", "2nd", 20) | ||||||
|  |  | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 50 byscore limit 0 2") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 50 byscore limit 0 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -288,7 +276,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "2,one,2nd") | 			testx.AssertEqual(t, conn.Out(), "2,one,2nd") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 50 byscore limit 1 2") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 50 byscore limit 1 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -296,7 +284,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "2,2nd,two") | 			testx.AssertEqual(t, conn.Out(), "2,2nd,two") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 50 byscore limit 2 5") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 50 byscore limit 2 5") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -304,7 +292,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "2,two,thr") | 			testx.AssertEqual(t, conn.Out(), "2,two,thr") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRange]("zrange key 0 50 byscore limit 1 -1") | 			cmd := redis.MustParse(ParseZRange, "zrange key 0 50 byscore limit 1 -1") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -320,7 +308,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "thr", 3) | 		_, _ = db.ZSet().Add("key", "thr", 3) | ||||||
| 		_, _ = db.ZSet().Add("key", "2nd", 2) | 		_, _ = db.ZSet().Add("key", "2nd", 2) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRange]("zrange key 0 5 withscores") | 		cmd := redis.MustParse(ParseZRange, "zrange key 0 5 withscores") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -335,7 +323,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "thr", 3) | 		_, _ = db.ZSet().Add("key", "thr", 3) | ||||||
| 		_, _ = db.ZSet().Add("key", "2nd", 2) | 		_, _ = db.ZSet().Add("key", "2nd", 2) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRange]("zrange key -2 -1") | 		cmd := redis.MustParse(ParseZRange, "zrange key -2 -1") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -346,7 +334,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRange]("zrange key 0 1") | 		cmd := redis.MustParse(ParseZRange, "zrange key 0 1") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -358,7 +346,7 @@ func TestZRangeExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("key", "value") | 		_ = db.Str().Set("key", "value") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRange]("zrange key 0 1") | 		cmd := redis.MustParse(ParseZRange, "zrange key 0 1") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -10,22 +10,22 @@ import ( | |||||||
| // https://redis.io/commands/zrangebyscore | // https://redis.io/commands/zrangebyscore | ||||||
| type ZRangeByScore struct { | type ZRangeByScore struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key        string | 	key        string | ||||||
| 	Min        float64 | 	min        float64 | ||||||
| 	Max        float64 | 	max        float64 | ||||||
| 	WithScores bool | 	withScores bool | ||||||
| 	Offset     int | 	offset     int | ||||||
| 	Count      int | 	count      int | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseZRangeByScore(b redis.BaseCmd) (*ZRangeByScore, error) { | func ParseZRangeByScore(b redis.BaseCmd) (*ZRangeByScore, error) { | ||||||
| 	cmd := &ZRangeByScore{BaseCmd: b} | 	cmd := &ZRangeByScore{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Float(&cmd.Min), | 		parser.Float(&cmd.min), | ||||||
| 		parser.Float(&cmd.Max), | 		parser.Float(&cmd.max), | ||||||
| 		parser.Flag("withscores", &cmd.WithScores), | 		parser.Flag("withscores", &cmd.withScores), | ||||||
| 		parser.Named("limit", parser.Int(&cmd.Offset), parser.Int(&cmd.Count)), | 		parser.Named("limit", parser.Int(&cmd.offset), parser.Int(&cmd.count)), | ||||||
| 	).Required(3).Run(cmd.Args()) | 	).Required(3).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -34,14 +34,14 @@ func ParseZRangeByScore(b redis.BaseCmd) (*ZRangeByScore, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ZRangeByScore) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ZRangeByScore) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	rang := red.ZSet().RangeWith(cmd.Key).ByScore(cmd.Min, cmd.Max) | 	rang := red.ZSet().RangeWith(cmd.key).ByScore(cmd.min, cmd.max) | ||||||
|  |  | ||||||
| 	// limit and offset | 	// limit and offset | ||||||
| 	if cmd.Offset > 0 { | 	if cmd.offset > 0 { | ||||||
| 		rang = rang.Offset(cmd.Offset) | 		rang = rang.Offset(cmd.offset) | ||||||
| 	} | 	} | ||||||
| 	if cmd.Count > 0 { | 	if cmd.count > 0 { | ||||||
| 		rang = rang.Count(cmd.Count) | 		rang = rang.Count(cmd.count) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// run the command | 	// run the command | ||||||
| @@ -52,7 +52,7 @@ func (cmd *ZRangeByScore) Run(w redis.Writer, red redis.Redka) (any, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// write the response with/without scores | 	// write the response with/without scores | ||||||
| 	if cmd.WithScores { | 	if cmd.withScores { | ||||||
| 		w.WriteArray(len(items) * 2) | 		w.WriteArray(len(items) * 2) | ||||||
| 		for _, item := range items { | 		for _, item := range items { | ||||||
| 			w.WriteBulk(item.Elem) | 			w.WriteBulk(item.Elem) | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package zset_test | package zset | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/zset" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/rzset" | 	"github.com/nalgeon/redka/internal/rzset" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| @@ -12,81 +10,72 @@ import ( | |||||||
|  |  | ||||||
| func TestZRangeByScoreParse(t *testing.T) { | func TestZRangeByScoreParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want ZRangeByScore | ||||||
| 		want zset.ZRangeByScore |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrangebyscore", | 			cmd:  "zrangebyscore", | ||||||
| 			args: command.BuildArgs("zrangebyscore"), | 			want: ZRangeByScore{}, | ||||||
| 			want: zset.ZRangeByScore{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrangebyscore key", | 			cmd:  "zrangebyscore key", | ||||||
| 			args: command.BuildArgs("zrangebyscore", "key"), | 			want: ZRangeByScore{}, | ||||||
| 			want: zset.ZRangeByScore{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrangebyscore key 11", | 			cmd:  "zrangebyscore key 11", | ||||||
| 			args: command.BuildArgs("zrangebyscore", "key", "11"), | 			want: ZRangeByScore{}, | ||||||
| 			want: zset.ZRangeByScore{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrangebyscore key 11 22", | 			cmd:  "zrangebyscore key 11 22", | ||||||
| 			args: command.BuildArgs("zrangebyscore", "key", "11", "22"), | 			want: ZRangeByScore{key: "key", min: 11.0, max: 22.0}, | ||||||
| 			want: zset.ZRangeByScore{Key: "key", Min: 11.0, Max: 22.0}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrangebyscore key exclusive", | 			cmd:  "zrangebyscore key (1 (2", | ||||||
| 			args: command.BuildArgs("zrangebyscore", "key", "(1", "(2"), | 			want: ZRangeByScore{}, | ||||||
| 			want: zset.ZRangeByScore{}, |  | ||||||
| 			err:  redis.ErrInvalidFloat, | 			err:  redis.ErrInvalidFloat, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrangebyscore key 11 22 limit 10", | 			cmd:  "zrangebyscore key 11 22 limit 10", | ||||||
| 			args: command.BuildArgs("zrangebyscore", "key", "11", "22", "limit", "10"), | 			want: ZRangeByScore{}, | ||||||
| 			want: zset.ZRangeByScore{}, |  | ||||||
| 			err:  redis.ErrSyntaxError, | 			err:  redis.ErrSyntaxError, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrangebyscore key 11 22 limit 10 5", | 			cmd:  "zrangebyscore key 11 22 limit 10 5", | ||||||
| 			args: command.BuildArgs("zrangebyscore", "key", "11", "22", "limit", "10", "5"), | 			want: ZRangeByScore{key: "key", min: 11.0, max: 22.0, offset: 10, count: 5}, | ||||||
| 			want: zset.ZRangeByScore{Key: "key", Min: 11.0, Max: 22.0, Offset: 10, Count: 5}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrangebyscore key 11 22 withscores", | 			cmd:  "zrangebyscore key 11 22 withscores", | ||||||
| 			args: command.BuildArgs("zrangebyscore", "key", "11", "22", "withscores"), | 			want: ZRangeByScore{key: "key", min: 11.0, max: 22.0, withScores: true}, | ||||||
| 			want: zset.ZRangeByScore{Key: "key", Min: 11.0, Max: 22.0, WithScores: true}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrangebyscore key 11 22 limit 10 5 withscores", | 			cmd: "zrangebyscore key 11 22 limit 10 5 withscores", | ||||||
| 			args: command.BuildArgs("zrangebyscore", "key", "11", "22", | 			want: ZRangeByScore{ | ||||||
| 				"limit", "10", "5", "withscores"), | 				key: "key", min: 11.0, max: 22.0, | ||||||
| 			want: zset.ZRangeByScore{Key: "key", Min: 11.0, Max: 22.0, | 				offset: 10, count: 5, | ||||||
| 				Offset: 10, Count: 5, WithScores: true}, | 				withScores: true, | ||||||
|  | 			}, | ||||||
| 			err: nil, | 			err: nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseZRangeByScore, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*zset.ZRangeByScore) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.min, test.want.min) | ||||||
| 				testx.AssertEqual(t, cm.Min, test.want.Min) | 				testx.AssertEqual(t, cmd.max, test.want.max) | ||||||
| 				testx.AssertEqual(t, cm.Max, test.want.Max) | 				testx.AssertEqual(t, cmd.offset, test.want.offset) | ||||||
| 				testx.AssertEqual(t, cm.Offset, test.want.Offset) | 				testx.AssertEqual(t, cmd.count, test.want.count) | ||||||
| 				testx.AssertEqual(t, cm.Count, test.want.Count) | 				testx.AssertEqual(t, cmd.withScores, test.want.withScores) | ||||||
| 				testx.AssertEqual(t, cm.WithScores, test.want.WithScores) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -102,7 +91,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "2nd", 20) | 		_, _ = db.ZSet().Add("key", "2nd", 20) | ||||||
|  |  | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 0 10") | 			cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 0 10") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -110,7 +99,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "1,one") | 			testx.AssertEqual(t, conn.Out(), "1,one") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 0 50") | 			cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 0 50") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -118,7 +107,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "4,one,2nd,two,thr") | 			testx.AssertEqual(t, conn.Out(), "4,one,2nd,two,thr") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 30 50") | 			cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 30 50") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -126,7 +115,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "1,thr") | 			testx.AssertEqual(t, conn.Out(), "1,thr") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 40 50") | 			cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 40 50") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -143,7 +132,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "2nd", 20) | 		_, _ = db.ZSet().Add("key", "2nd", 20) | ||||||
|  |  | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 0 50 limit 0 2") | 			cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 0 50 limit 0 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -151,7 +140,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "2,one,2nd") | 			testx.AssertEqual(t, conn.Out(), "2,one,2nd") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 0 50 limit 1 2") | 			cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 0 50 limit 1 2") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -159,7 +148,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "2,2nd,two") | 			testx.AssertEqual(t, conn.Out(), "2,2nd,two") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 0 50 limit 2 5") | 			cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 0 50 limit 2 5") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -167,7 +156,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 			testx.AssertEqual(t, conn.Out(), "2,two,thr") | 			testx.AssertEqual(t, conn.Out(), "2,two,thr") | ||||||
| 		} | 		} | ||||||
| 		{ | 		{ | ||||||
| 			cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 0 50 limit 1 -1") | 			cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 0 50 limit 1 -1") | ||||||
| 			conn := redis.NewFakeConn() | 			conn := redis.NewFakeConn() | ||||||
| 			res, err := cmd.Run(conn, red) | 			res, err := cmd.Run(conn, red) | ||||||
| 			testx.AssertNoErr(t, err) | 			testx.AssertNoErr(t, err) | ||||||
| @@ -183,7 +172,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "thr", 30) | 		_, _ = db.ZSet().Add("key", "thr", 30) | ||||||
| 		_, _ = db.ZSet().Add("key", "2nd", 20) | 		_, _ = db.ZSet().Add("key", "2nd", 20) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 10 50 withscores") | 		cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 10 50 withscores") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -198,7 +187,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "thr", -30) | 		_, _ = db.ZSet().Add("key", "thr", -30) | ||||||
| 		_, _ = db.ZSet().Add("key", "2nd", -20) | 		_, _ = db.ZSet().Add("key", "2nd", -20) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key -20 -10") | 		cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key -20 -10") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -209,7 +198,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 0 1") | 		cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 0 1") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -221,7 +210,7 @@ func TestZRangeByScoreExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("key", "value") | 		_ = db.Str().Set("key", "value") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRangeByScore]("zrangebyscore key 0 1") | 		cmd := redis.MustParse(ParseZRangeByScore, "zrangebyscore key 0 1") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -11,17 +11,17 @@ import ( | |||||||
| // https://redis.io/commands/zrank | // https://redis.io/commands/zrank | ||||||
| type ZRank struct { | type ZRank struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key       string | 	key       string | ||||||
| 	Member    string | 	member    string | ||||||
| 	WithScore bool | 	withScore bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseZRank(b redis.BaseCmd) (*ZRank, error) { | func ParseZRank(b redis.BaseCmd) (*ZRank, error) { | ||||||
| 	cmd := &ZRank{BaseCmd: b} | 	cmd := &ZRank{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.String(&cmd.Member), | 		parser.String(&cmd.member), | ||||||
| 		parser.Flag("withscore", &cmd.WithScore), | 		parser.Flag("withscore", &cmd.withScore), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -30,7 +30,7 @@ func ParseZRank(b redis.BaseCmd) (*ZRank, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ZRank) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ZRank) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	rank, score, err := red.ZSet().GetRank(cmd.Key, cmd.Member) | 	rank, score, err := red.ZSet().GetRank(cmd.key, cmd.member) | ||||||
| 	if err == core.ErrNotFound { | 	if err == core.ErrNotFound { | ||||||
| 		w.WriteNull() | 		w.WriteNull() | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| @@ -39,7 +39,7 @@ func (cmd *ZRank) Run(w redis.Writer, red redis.Redka) (any, error) { | |||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	if cmd.WithScore { | 	if cmd.withScore { | ||||||
| 		w.WriteArray(2) | 		w.WriteArray(2) | ||||||
| 		w.WriteInt(rank) | 		w.WriteInt(rank) | ||||||
| 		redis.WriteFloat(w, score) | 		redis.WriteFloat(w, score) | ||||||
|   | |||||||
| @@ -1,56 +1,48 @@ | |||||||
| package zset_test | package zset | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/nalgeon/redka/internal/command" |  | ||||||
| 	"github.com/nalgeon/redka/internal/command/zset" |  | ||||||
| 	"github.com/nalgeon/redka/internal/redis" | 	"github.com/nalgeon/redka/internal/redis" | ||||||
| 	"github.com/nalgeon/redka/internal/testx" | 	"github.com/nalgeon/redka/internal/testx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestZRankParse(t *testing.T) { | func TestZRankParse(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		cmd  string | ||||||
| 		args [][]byte | 		want ZRank | ||||||
| 		want zset.ZRank |  | ||||||
| 		err  error | 		err  error | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrank", | 			cmd:  "zrank", | ||||||
| 			args: command.BuildArgs("zrank"), | 			want: ZRank{}, | ||||||
| 			want: zset.ZRank{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrank key", | 			cmd:  "zrank key", | ||||||
| 			args: command.BuildArgs("zrank", "key"), | 			want: ZRank{}, | ||||||
| 			want: zset.ZRank{}, |  | ||||||
| 			err:  redis.ErrInvalidArgNum, | 			err:  redis.ErrInvalidArgNum, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrank key member", | 			cmd:  "zrank key member", | ||||||
| 			args: command.BuildArgs("zrank", "key", "member"), | 			want: ZRank{key: "key", member: "member"}, | ||||||
| 			want: zset.ZRank{Key: "key", Member: "member"}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "zrank key member withscore", | 			cmd:  "zrank key member withscore", | ||||||
| 			args: command.BuildArgs("zrank", "key", "member", "withscore"), | 			want: ZRank{key: "key", member: "member", withScore: true}, | ||||||
| 			want: zset.ZRank{Key: "key", Member: "member", WithScore: true}, |  | ||||||
| 			err:  nil, | 			err:  nil, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		t.Run(test.name, func(t *testing.T) { | 		t.Run(test.cmd, func(t *testing.T) { | ||||||
| 			cmd, err := command.Parse(test.args) | 			cmd, err := redis.Parse(ParseZRank, test.cmd) | ||||||
| 			testx.AssertEqual(t, err, test.err) | 			testx.AssertEqual(t, err, test.err) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				cm := cmd.(*zset.ZRank) | 				testx.AssertEqual(t, cmd.key, test.want.key) | ||||||
| 				testx.AssertEqual(t, cm.Key, test.want.Key) | 				testx.AssertEqual(t, cmd.member, test.want.member) | ||||||
| 				testx.AssertEqual(t, cm.Member, test.want.Member) | 				testx.AssertEqual(t, cmd.withScore, test.want.withScore) | ||||||
| 				testx.AssertEqual(t, cm.WithScore, test.want.WithScore) |  | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -63,7 +55,7 @@ func TestZRankExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "one", 11) | 		_, _ = db.ZSet().Add("key", "one", 11) | ||||||
| 		_, _ = db.ZSet().Add("key", "two", 22) | 		_, _ = db.ZSet().Add("key", "two", 22) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRank]("zrank key two") | 		cmd := redis.MustParse(ParseZRank, "zrank key two") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -76,7 +68,7 @@ func TestZRankExec(t *testing.T) { | |||||||
| 		_, _ = db.ZSet().Add("key", "one", 11) | 		_, _ = db.ZSet().Add("key", "one", 11) | ||||||
| 		_, _ = db.ZSet().Add("key", "two", 22) | 		_, _ = db.ZSet().Add("key", "two", 22) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRank]("zrank key two withscore") | 		cmd := redis.MustParse(ParseZRank, "zrank key two withscore") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -88,7 +80,7 @@ func TestZRankExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_, _ = db.ZSet().Add("key", "one", 11) | 		_, _ = db.ZSet().Add("key", "one", 11) | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRank]("zrank key two") | 		cmd := redis.MustParse(ParseZRank, "zrank key two") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -99,7 +91,7 @@ func TestZRankExec(t *testing.T) { | |||||||
| 		db, red := getDB(t) | 		db, red := getDB(t) | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRank]("zrank key two") | 		cmd := redis.MustParse(ParseZRank, "zrank key two") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
| @@ -111,7 +103,7 @@ func TestZRankExec(t *testing.T) { | |||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		_ = db.Str().Set("key", "value") | 		_ = db.Str().Set("key", "value") | ||||||
|  |  | ||||||
| 		cmd := command.MustParse[*zset.ZRank]("zrank key two") | 		cmd := redis.MustParse(ParseZRank, "zrank key two") | ||||||
| 		conn := redis.NewFakeConn() | 		conn := redis.NewFakeConn() | ||||||
| 		res, err := cmd.Run(conn, red) | 		res, err := cmd.Run(conn, red) | ||||||
| 		testx.AssertNoErr(t, err) | 		testx.AssertNoErr(t, err) | ||||||
|   | |||||||
| @@ -10,15 +10,15 @@ import ( | |||||||
| // https://redis.io/commands/zrem | // https://redis.io/commands/zrem | ||||||
| type ZRem struct { | type ZRem struct { | ||||||
| 	redis.BaseCmd | 	redis.BaseCmd | ||||||
| 	Key     string | 	key     string | ||||||
| 	Members []any | 	members []any | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseZRem(b redis.BaseCmd) (*ZRem, error) { | func ParseZRem(b redis.BaseCmd) (*ZRem, error) { | ||||||
| 	cmd := &ZRem{BaseCmd: b} | 	cmd := &ZRem{BaseCmd: b} | ||||||
| 	err := parser.New( | 	err := parser.New( | ||||||
| 		parser.String(&cmd.Key), | 		parser.String(&cmd.key), | ||||||
| 		parser.Anys(&cmd.Members), | 		parser.Anys(&cmd.members), | ||||||
| 	).Required(2).Run(cmd.Args()) | 	).Required(2).Run(cmd.Args()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -27,7 +27,7 @@ func ParseZRem(b redis.BaseCmd) (*ZRem, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (cmd *ZRem) Run(w redis.Writer, red redis.Redka) (any, error) { | func (cmd *ZRem) Run(w redis.Writer, red redis.Redka) (any, error) { | ||||||
| 	n, err := red.ZSet().Delete(cmd.Key, cmd.Members...) | 	n, err := red.ZSet().Delete(cmd.key, cmd.members...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		w.WriteError(cmd.Error(err)) | 		w.WriteError(cmd.Error(err)) | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Anton
					Anton