Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LCS #767

Merged
merged 4 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion rueidiscompat/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ type CoreCmdable 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
// TODO LCS(ctx context.Context, q *LCSQuery) *LCSCmd
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 any) *IntCmd
LInsertBefore(ctx context.Context, key string, pivot, value any) *IntCmd
Expand Down Expand Up @@ -1581,6 +1581,34 @@ func (c *Compat) BRPopLPush(ctx context.Context, source, destination string, tim
return newStringCmd(resp)
}

func (c *Compat) LCS(ctx context.Context, q *LCSQuery) *LCSCmd {
var cmd cmds.Completed
var readType uint8

_cmd := cmds.Incomplete(c.client.B().Lcs().Key1(q.Key1).Key2(q.Key2))

if q.Len {
readType = uint8(2)
cmd = cmds.LcsKey2(_cmd).Len().Build()
} else if q.Idx {
readType = uint8(3)
if q.MinMatchLen > 0 && q.WithMatchLen {
cmd = cmds.LcsKey2(_cmd).Idx().Minmatchlen(int64(q.MinMatchLen)).Withmatchlen().Build()
} else if q.MinMatchLen > 0 {
cmd = cmds.LcsKey2(_cmd).Idx().Minmatchlen(int64(q.MinMatchLen)).Build()
} else if q.WithMatchLen {
cmd = cmds.LcsKey2(_cmd).Idx().Withmatchlen().Build()
} else {
cmd = cmds.LcsKey2(_cmd).Idx().Build()
}
} else {
readType = uint8(1)
cmd = cmds.LcsKey2(_cmd).Build()
}

return newLCSCmd(c.client.Do(ctx, cmd), readType)
}

func (c *Compat) LIndex(ctx context.Context, key string, index int64) *StringCmd {
cmd := c.client.B().Lindex().Key(key).Index(index).Build()
resp := c.client.Do(ctx, cmd)
Expand Down
191 changes: 191 additions & 0 deletions rueidiscompat/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -5107,3 +5107,194 @@ func newSlowLogCmd(res rueidis.RedisResult) *SlowLogCmd {
cmd.from(res)
return cmd
}

// 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[*LCSMatch]

// 1: match string
// 2: match len
// 3: match idx LCSMatch
readType uint8
}

func newLCSCmd(res rueidis.RedisResult, readType uint8) *LCSCmd {
cmd := &LCSCmd{readType: readType}
cmd.from(res)
return cmd
}

func (cmd *LCSCmd) SetVal(val *LCSMatch) {
cmd.val = val
}

func (cmd *LCSCmd) SetErr(err error) {
cmd.err = err
}

func (cmd *LCSCmd) Val() *LCSMatch {
return cmd.val
}

func (cmd *LCSCmd) Err() error {
return cmd.err
}

func (cmd *LCSCmd) Result() (*LCSMatch, error) {
return cmd.val, cmd.err
}

func (cmd *LCSCmd) from(res rueidis.RedisResult) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parsing function seems incorrect. Is it compatible with https://github.com/redis/go-redis/blob/196fc9b21ac460f0d5251d349e4249e2ffedb9ff/command.go#L4510?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. I misunderstood what was required. I’ve added a new commit with the necessary changes.

lcs := &LCSMatch{}
var err error

switch cmd.readType {
case 1:
// match string
if lcs.MatchString, err = res.ToString(); err != nil {
cmd.SetErr(err)
return
}
case 2:
// match len
if lcs.Len, err = res.AsInt64(); err != nil {
cmd.SetErr(err)
return
}
case 3:
// read LCSMatch
if msgMap, err := res.AsMap(); err != nil {
cmd.SetErr(err)
return
} else {
// Validate length (should have exactly 2 keys: "matches" and "len")
if len(msgMap) != 2 {
cmd.SetErr(fmt.Errorf("redis: got %d elements in the map, wanted %d", len(msgMap), 2))
return
}

// read matches or len field
for key, value := range msgMap {
switch key {
case "matches":
// read array of matched positions
matches, err := cmd.readMatchedPositions(value)
if err != nil {
cmd.SetErr(err)
return
}
lcs.Matches = matches

case "len":
// read match length
matchLen, err := value.AsInt64()
if err != nil {
cmd.SetErr(err)
return
}
lcs.Len = matchLen
}
}
}
}

cmd.val = lcs
}

func (cmd *LCSCmd) readMatchedPositions(res rueidis.RedisMessage) ([]LCSMatchedPosition, error) {
val, err := res.ToArray()
if err != nil {
return nil, err
}

n := len(val)
positions := make([]LCSMatchedPosition, n)

for i := 0; i < n; i++ {
pn, err := val[i].ToArray()
if err != nil {
return nil, err
}

if len(pn) < 2 {
return nil, fmt.Errorf("invalid position format")
}

key1, err := cmd.readPosition(pn[0])
if err != nil {
return nil, err
}

key2, err := cmd.readPosition(pn[1])
if err != nil {
return nil, err
}

pos := LCSMatchedPosition{
Key1: key1,
Key2: key2,
}

// Read match length if WithMatchLen is true
if len(pn) > 2 {
if pos.MatchLen, err = pn[2].AsInt64(); err != nil {
return nil, err
}
}

positions[i] = pos
}

return positions, nil
}

func (cmd *LCSCmd) readPosition(res rueidis.RedisMessage) (LCSPosition, error) {
posArray, err := res.ToArray()
if err != nil {
return LCSPosition{}, err
}
if len(posArray) != 2 {
return LCSPosition{}, fmt.Errorf("redis: got %d elements in the array, wanted %d", len(posArray), 2)
}

start, err := posArray[0].AsInt64()
if err != nil {
return LCSPosition{}, err
}

end, err := posArray[1].AsInt64()
if err != nil {
return LCSPosition{}, err
}

return LCSPosition{Start: start, End: end}, nil
}
6 changes: 6 additions & 0 deletions rueidiscompat/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,12 @@ func (c *Pipeline) BRPopLPush(ctx context.Context, source, destination string, t
return ret
}

func (c *Pipeline) LCS(ctx context.Context, q *LCSQuery) *LCSCmd {
ret := c.comp.LCS(ctx, q)
c.rets = append(c.rets, ret)
return ret
}

func (c *Pipeline) LIndex(ctx context.Context, key string, index int64) *StringCmd {
ret := c.comp.LIndex(ctx, key, index)
c.rets = append(c.rets, ret)
Expand Down