Add support for LCS Command (#2480)

* feat: initial set up for LCSCmd

* feat: adding test for LCS with no additional params

* feat: adding test for LCS with LEN param

* feat: resolving conflicts in command.go

* fix: changing reply and query structure

* feat: commands test for lcs and lcs with len

* fix: using dynamic args length

* fix: read

* fix: Cmd init

* sorting out the LCS commands

Signed-off-by: monkey92t <golang@88.com>

* fix: Adding error case test

* fix: adding correct error message

* fix: removed blank lines

---------

Signed-off-by: monkey92t <golang@88.com>
Co-authored-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com>
Co-authored-by: monkey92t <golang@88.com>
This commit is contained in:
Anurag Bandyopadhyay 2023-03-24 13:08:11 +05:30 committed by GitHub
parent 38aa0b7792
commit 6790337e5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 267 additions and 4 deletions

View File

@ -3993,9 +3993,178 @@ func (cmd *FunctionListCmd) readFunctions(rd *proto.Reader) ([]Function, error)
return functions, nil
}
type FilterBy struct {
Module string
ACLCat string
Pattern string
//------------------------------------------------------------------------------
// LCSQuery is a parameter used for the LCS command
type LCSQuery struct {
Key1 string
Key2 string
Len bool
Idx bool
MinMatchLen int
WithMatchLen bool
}
// LCSMatch is the result set of the LCS command.
type LCSMatch struct {
MatchString string
Matches []LCSMatchedPosition
Len int64
}
type LCSMatchedPosition struct {
Key1 LCSPosition
Key2 LCSPosition
// only for withMatchLen is true
MatchLen int64
}
type LCSPosition struct {
Start int64
End int64
}
type LCSCmd struct {
baseCmd
// 1: match string
// 2: match len
// 3: match idx LCSMatch
readType uint8
val *LCSMatch
}
func NewLCSCmd(ctx context.Context, q *LCSQuery) *LCSCmd {
args := make([]interface{}, 3, 7)
args[0] = "lcs"
args[1] = q.Key1
args[2] = q.Key2
cmd := &LCSCmd{readType: 1}
if q.Len {
cmd.readType = 2
args = append(args, "len")
} else if q.Idx {
cmd.readType = 3
args = append(args, "idx")
if q.MinMatchLen != 0 {
args = append(args, "minmatchlen", q.MinMatchLen)
}
if q.WithMatchLen {
args = append(args, "withmatchlen")
}
}
cmd.baseCmd = baseCmd{
ctx: ctx,
args: args,
}
return cmd
}
func (cmd *LCSCmd) SetVal(val *LCSMatch) {
cmd.val = val
}
func (cmd *LCSCmd) String() string {
return cmdString(cmd, cmd.val)
}
func (cmd *LCSCmd) Val() *LCSMatch {
return cmd.val
}
func (cmd *LCSCmd) Result() (*LCSMatch, error) {
return cmd.val, cmd.err
}
func (cmd *LCSCmd) readReply(rd *proto.Reader) (err error) {
lcs := &LCSMatch{}
switch cmd.readType {
case 1:
// match string
if lcs.MatchString, err = rd.ReadString(); err != nil {
return err
}
case 2:
// match len
if lcs.Len, err = rd.ReadInt(); err != nil {
return err
}
case 3:
// read LCSMatch
if err = rd.ReadFixedMapLen(2); err != nil {
return err
}
// read matches or len field
for i := 0; i < 2; i++ {
key, err := rd.ReadString()
if err != nil {
return err
}
switch key {
case "matches":
// read array of matched positions
if lcs.Matches, err = cmd.readMatchedPositions(rd); err != nil {
return err
}
case "len":
// read match length
if lcs.Len, err = rd.ReadInt(); err != nil {
return err
}
}
}
}
cmd.val = lcs
return nil
}
func (cmd *LCSCmd) readMatchedPositions(rd *proto.Reader) ([]LCSMatchedPosition, error) {
n, err := rd.ReadArrayLen()
if err != nil {
return nil, err
}
positions := make([]LCSMatchedPosition, n)
for i := 0; i < n; i++ {
pn, err := rd.ReadArrayLen()
if err != nil {
return nil, err
}
if positions[i].Key1, err = cmd.readPosition(rd); err != nil {
return nil, err
}
if positions[i].Key2, err = cmd.readPosition(rd); err != nil {
return nil, err
}
// read match length if WithMatchLen is true
if pn > 2 {
if positions[i].MatchLen, err = rd.ReadInt(); err != nil {
return nil, err
}
}
}
return positions, nil
}
func (cmd *LCSCmd) readPosition(rd *proto.Reader) (pos LCSPosition, err error) {
if err = rd.ReadFixedArrayLen(2); err != nil {
return pos, err
}
if pos.Start, err = rd.ReadInt(); err != nil {
return pos, err
}
if pos.End, err = rd.ReadInt(); err != nil {
return pos, err
}
return pos, nil
}

View File

@ -227,6 +227,7 @@ type Cmdable interface {
BLMPop(ctx context.Context, timeout time.Duration, direction string, count int64, keys ...string) *KeyValuesCmd
BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd
BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd
LCS(ctx context.Context, q *LCSQuery) *LCSCmd
LIndex(ctx context.Context, key string, index int64) *StringCmd
LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd
LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd
@ -543,6 +544,13 @@ func (c cmdable) Command(ctx context.Context) *CommandsInfoCmd {
return cmd
}
// FilterBy is used for the `CommandList` command parameter.
type FilterBy struct {
Module string
ACLCat string
Pattern string
}
func (c cmdable) CommandList(ctx context.Context, filter *FilterBy) *StringSliceCmd {
args := make([]interface{}, 0, 5)
args = append(args, "command", "list")
@ -1524,6 +1532,12 @@ func (c cmdable) BRPopLPush(ctx context.Context, source, destination string, tim
return cmd
}
func (c cmdable) LCS(ctx context.Context, q *LCSQuery) *LCSCmd {
cmd := NewLCSCmd(ctx, q)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) LIndex(ctx context.Context, key string, index int64) *StringCmd {
cmd := NewStringCmd(ctx, "lindex", key, index)
_ = c(ctx, cmd)

View File

@ -2251,6 +2251,86 @@ var _ = Describe("Commands", func() {
Expect(v).To(Equal("c"))
})
It("should LCS", func() {
err := client.MSet(ctx, "key1", "ohmytext", "key2", "mynewtext").Err()
Expect(err).NotTo(HaveOccurred())
lcs, err := client.LCS(ctx, &redis.LCSQuery{
Key1: "key1",
Key2: "key2",
}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(lcs.MatchString).To(Equal("mytext"))
lcs, err = client.LCS(ctx, &redis.LCSQuery{
Key1: "nonexistent_key1",
Key2: "key2",
}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(lcs.MatchString).To(Equal(""))
lcs, err = client.LCS(ctx, &redis.LCSQuery{
Key1: "key1",
Key2: "key2",
Len: true,
}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(lcs.MatchString).To(Equal(""))
Expect(lcs.Len).To(Equal(int64(6)))
lcs, err = client.LCS(ctx, &redis.LCSQuery{
Key1: "key1",
Key2: "key2",
Idx: true,
}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(lcs.MatchString).To(Equal(""))
Expect(lcs.Len).To(Equal(int64(6)))
Expect(lcs.Matches).To(Equal([]redis.LCSMatchedPosition{
{
Key1: redis.LCSPosition{Start: 4, End: 7},
Key2: redis.LCSPosition{Start: 5, End: 8},
MatchLen: 0,
},
{
Key1: redis.LCSPosition{Start: 2, End: 3},
Key2: redis.LCSPosition{Start: 0, End: 1},
MatchLen: 0,
},
}))
lcs, err = client.LCS(ctx, &redis.LCSQuery{
Key1: "key1",
Key2: "key2",
Idx: true,
MinMatchLen: 3,
WithMatchLen: true,
}).Result()
Expect(err).NotTo(HaveOccurred())
Expect(lcs.MatchString).To(Equal(""))
Expect(lcs.Len).To(Equal(int64(6)))
Expect(lcs.Matches).To(Equal([]redis.LCSMatchedPosition{
{
Key1: redis.LCSPosition{Start: 4, End: 7},
Key2: redis.LCSPosition{Start: 5, End: 8},
MatchLen: 4,
},
}))
_, err = client.Set(ctx, "keywithstringvalue", "golang", 0).Result()
Expect(err).NotTo(HaveOccurred())
_, err = client.LPush(ctx, "keywithnonstringvalue", "somevalue").Result()
Expect(err).NotTo(HaveOccurred())
_, err = client.LCS(ctx, &redis.LCSQuery{
Key1: "keywithstringvalue",
Key2: "keywithnonstringvalue",
}).Result()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("ERR The specified keys must contain string values"))
})
It("should LIndex", func() {
lPush := client.LPush(ctx, "list", "World")
Expect(lPush.Err()).NotTo(HaveOccurred())