Skip to content

Commit 6790337

Browse files
Add support for LCS Command (redis#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>
1 parent 38aa0b7 commit 6790337

File tree

3 files changed

+267
-4
lines changed

3 files changed

+267
-4
lines changed

command.go

Lines changed: 173 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3993,9 +3993,178 @@ func (cmd *FunctionListCmd) readFunctions(rd *proto.Reader) ([]Function, error)
39933993
return functions, nil
39943994
}
39953995

3996-
type FilterBy struct {
3997-
Module string
3998-
ACLCat string
3999-
Pattern string
3996+
//------------------------------------------------------------------------------
3997+
3998+
// LCSQuery is a parameter used for the LCS command
3999+
type LCSQuery struct {
4000+
Key1 string
4001+
Key2 string
4002+
Len bool
4003+
Idx bool
4004+
MinMatchLen int
4005+
WithMatchLen bool
4006+
}
4007+
4008+
// LCSMatch is the result set of the LCS command.
4009+
type LCSMatch struct {
4010+
MatchString string
4011+
Matches []LCSMatchedPosition
4012+
Len int64
4013+
}
4014+
4015+
type LCSMatchedPosition struct {
4016+
Key1 LCSPosition
4017+
Key2 LCSPosition
4018+
4019+
// only for withMatchLen is true
4020+
MatchLen int64
4021+
}
4022+
4023+
type LCSPosition struct {
4024+
Start int64
4025+
End int64
4026+
}
4027+
4028+
type LCSCmd struct {
4029+
baseCmd
4030+
4031+
// 1: match string
4032+
// 2: match len
4033+
// 3: match idx LCSMatch
4034+
readType uint8
4035+
val *LCSMatch
4036+
}
4037+
4038+
func NewLCSCmd(ctx context.Context, q *LCSQuery) *LCSCmd {
4039+
args := make([]interface{}, 3, 7)
4040+
args[0] = "lcs"
4041+
args[1] = q.Key1
4042+
args[2] = q.Key2
4043+
4044+
cmd := &LCSCmd{readType: 1}
4045+
if q.Len {
4046+
cmd.readType = 2
4047+
args = append(args, "len")
4048+
} else if q.Idx {
4049+
cmd.readType = 3
4050+
args = append(args, "idx")
4051+
if q.MinMatchLen != 0 {
4052+
args = append(args, "minmatchlen", q.MinMatchLen)
4053+
}
4054+
if q.WithMatchLen {
4055+
args = append(args, "withmatchlen")
4056+
}
4057+
}
4058+
cmd.baseCmd = baseCmd{
4059+
ctx: ctx,
4060+
args: args,
4061+
}
4062+
4063+
return cmd
4064+
}
4065+
4066+
func (cmd *LCSCmd) SetVal(val *LCSMatch) {
4067+
cmd.val = val
4068+
}
4069+
4070+
func (cmd *LCSCmd) String() string {
4071+
return cmdString(cmd, cmd.val)
4072+
}
4073+
4074+
func (cmd *LCSCmd) Val() *LCSMatch {
4075+
return cmd.val
4076+
}
4077+
4078+
func (cmd *LCSCmd) Result() (*LCSMatch, error) {
4079+
return cmd.val, cmd.err
4080+
}
4081+
4082+
func (cmd *LCSCmd) readReply(rd *proto.Reader) (err error) {
4083+
lcs := &LCSMatch{}
4084+
switch cmd.readType {
4085+
case 1:
4086+
// match string
4087+
if lcs.MatchString, err = rd.ReadString(); err != nil {
4088+
return err
4089+
}
4090+
case 2:
4091+
// match len
4092+
if lcs.Len, err = rd.ReadInt(); err != nil {
4093+
return err
4094+
}
4095+
case 3:
4096+
// read LCSMatch
4097+
if err = rd.ReadFixedMapLen(2); err != nil {
4098+
return err
4099+
}
4100+
4101+
// read matches or len field
4102+
for i := 0; i < 2; i++ {
4103+
key, err := rd.ReadString()
4104+
if err != nil {
4105+
return err
4106+
}
4107+
4108+
switch key {
4109+
case "matches":
4110+
// read array of matched positions
4111+
if lcs.Matches, err = cmd.readMatchedPositions(rd); err != nil {
4112+
return err
4113+
}
4114+
case "len":
4115+
// read match length
4116+
if lcs.Len, err = rd.ReadInt(); err != nil {
4117+
return err
4118+
}
4119+
}
4120+
}
4121+
}
4122+
4123+
cmd.val = lcs
4124+
return nil
40004125
}
40014126

4127+
func (cmd *LCSCmd) readMatchedPositions(rd *proto.Reader) ([]LCSMatchedPosition, error) {
4128+
n, err := rd.ReadArrayLen()
4129+
if err != nil {
4130+
return nil, err
4131+
}
4132+
4133+
positions := make([]LCSMatchedPosition, n)
4134+
for i := 0; i < n; i++ {
4135+
pn, err := rd.ReadArrayLen()
4136+
if err != nil {
4137+
return nil, err
4138+
}
4139+
4140+
if positions[i].Key1, err = cmd.readPosition(rd); err != nil {
4141+
return nil, err
4142+
}
4143+
if positions[i].Key2, err = cmd.readPosition(rd); err != nil {
4144+
return nil, err
4145+
}
4146+
4147+
// read match length if WithMatchLen is true
4148+
if pn > 2 {
4149+
if positions[i].MatchLen, err = rd.ReadInt(); err != nil {
4150+
return nil, err
4151+
}
4152+
}
4153+
}
4154+
4155+
return positions, nil
4156+
}
4157+
4158+
func (cmd *LCSCmd) readPosition(rd *proto.Reader) (pos LCSPosition, err error) {
4159+
if err = rd.ReadFixedArrayLen(2); err != nil {
4160+
return pos, err
4161+
}
4162+
if pos.Start, err = rd.ReadInt(); err != nil {
4163+
return pos, err
4164+
}
4165+
if pos.End, err = rd.ReadInt(); err != nil {
4166+
return pos, err
4167+
}
4168+
4169+
return pos, nil
4170+
}

commands.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ type Cmdable interface {
227227
BLMPop(ctx context.Context, timeout time.Duration, direction string, count int64, keys ...string) *KeyValuesCmd
228228
BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd
229229
BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd
230+
LCS(ctx context.Context, q *LCSQuery) *LCSCmd
230231
LIndex(ctx context.Context, key string, index int64) *StringCmd
231232
LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd
232233
LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd
@@ -543,6 +544,13 @@ func (c cmdable) Command(ctx context.Context) *CommandsInfoCmd {
543544
return cmd
544545
}
545546

547+
// FilterBy is used for the `CommandList` command parameter.
548+
type FilterBy struct {
549+
Module string
550+
ACLCat string
551+
Pattern string
552+
}
553+
546554
func (c cmdable) CommandList(ctx context.Context, filter *FilterBy) *StringSliceCmd {
547555
args := make([]interface{}, 0, 5)
548556
args = append(args, "command", "list")
@@ -1524,6 +1532,12 @@ func (c cmdable) BRPopLPush(ctx context.Context, source, destination string, tim
15241532
return cmd
15251533
}
15261534

1535+
func (c cmdable) LCS(ctx context.Context, q *LCSQuery) *LCSCmd {
1536+
cmd := NewLCSCmd(ctx, q)
1537+
_ = c(ctx, cmd)
1538+
return cmd
1539+
}
1540+
15271541
func (c cmdable) LIndex(ctx context.Context, key string, index int64) *StringCmd {
15281542
cmd := NewStringCmd(ctx, "lindex", key, index)
15291543
_ = c(ctx, cmd)

commands_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2251,6 +2251,86 @@ var _ = Describe("Commands", func() {
22512251
Expect(v).To(Equal("c"))
22522252
})
22532253

2254+
It("should LCS", func() {
2255+
err := client.MSet(ctx, "key1", "ohmytext", "key2", "mynewtext").Err()
2256+
Expect(err).NotTo(HaveOccurred())
2257+
2258+
lcs, err := client.LCS(ctx, &redis.LCSQuery{
2259+
Key1: "key1",
2260+
Key2: "key2",
2261+
}).Result()
2262+
2263+
Expect(err).NotTo(HaveOccurred())
2264+
Expect(lcs.MatchString).To(Equal("mytext"))
2265+
2266+
lcs, err = client.LCS(ctx, &redis.LCSQuery{
2267+
Key1: "nonexistent_key1",
2268+
Key2: "key2",
2269+
}).Result()
2270+
2271+
Expect(err).NotTo(HaveOccurred())
2272+
Expect(lcs.MatchString).To(Equal(""))
2273+
2274+
lcs, err = client.LCS(ctx, &redis.LCSQuery{
2275+
Key1: "key1",
2276+
Key2: "key2",
2277+
Len: true,
2278+
}).Result()
2279+
Expect(err).NotTo(HaveOccurred())
2280+
Expect(lcs.MatchString).To(Equal(""))
2281+
Expect(lcs.Len).To(Equal(int64(6)))
2282+
2283+
lcs, err = client.LCS(ctx, &redis.LCSQuery{
2284+
Key1: "key1",
2285+
Key2: "key2",
2286+
Idx: true,
2287+
}).Result()
2288+
Expect(err).NotTo(HaveOccurred())
2289+
Expect(lcs.MatchString).To(Equal(""))
2290+
Expect(lcs.Len).To(Equal(int64(6)))
2291+
Expect(lcs.Matches).To(Equal([]redis.LCSMatchedPosition{
2292+
{
2293+
Key1: redis.LCSPosition{Start: 4, End: 7},
2294+
Key2: redis.LCSPosition{Start: 5, End: 8},
2295+
MatchLen: 0,
2296+
},
2297+
{
2298+
Key1: redis.LCSPosition{Start: 2, End: 3},
2299+
Key2: redis.LCSPosition{Start: 0, End: 1},
2300+
MatchLen: 0,
2301+
},
2302+
}))
2303+
2304+
lcs, err = client.LCS(ctx, &redis.LCSQuery{
2305+
Key1: "key1",
2306+
Key2: "key2",
2307+
Idx: true,
2308+
MinMatchLen: 3,
2309+
WithMatchLen: true,
2310+
}).Result()
2311+
Expect(err).NotTo(HaveOccurred())
2312+
Expect(lcs.MatchString).To(Equal(""))
2313+
Expect(lcs.Len).To(Equal(int64(6)))
2314+
Expect(lcs.Matches).To(Equal([]redis.LCSMatchedPosition{
2315+
{
2316+
Key1: redis.LCSPosition{Start: 4, End: 7},
2317+
Key2: redis.LCSPosition{Start: 5, End: 8},
2318+
MatchLen: 4,
2319+
},
2320+
}))
2321+
2322+
_, err = client.Set(ctx, "keywithstringvalue", "golang", 0).Result()
2323+
Expect(err).NotTo(HaveOccurred())
2324+
_, err = client.LPush(ctx, "keywithnonstringvalue", "somevalue").Result()
2325+
Expect(err).NotTo(HaveOccurred())
2326+
_, err = client.LCS(ctx, &redis.LCSQuery{
2327+
Key1: "keywithstringvalue",
2328+
Key2: "keywithnonstringvalue",
2329+
}).Result()
2330+
Expect(err).To(HaveOccurred())
2331+
Expect(err.Error()).To(Equal("ERR The specified keys must contain string values"))
2332+
})
2333+
22542334
It("should LIndex", func() {
22552335
lPush := client.LPush(ctx, "list", "World")
22562336
Expect(lPush.Err()).NotTo(HaveOccurred())

0 commit comments

Comments
 (0)