mirror of
https://github.com/redis/go-redis.git
synced 2026-10-28 08:24:54 +08:00
* fix(command): set key position for 16 commands
The shared key resolver routes Ring shards, cluster slots, client-side
caching and full-duplex engines. For typed commands whose key is not
args[1] it fell back to args[1], or, on a warm cluster cache, to the
COMMAND first key, which is 0 for container and numkeys commands:
BITOP (all BitOp*), MIGRATE op / host instead of the key
OBJECT ENCODING/FREQ/IDLETIME/REFCOUNT, XINFO STREAM/GROUPS/CONSUMERS
subcommand instead of the key
LMPOP, SINTERCARD, ZINTERCARD, ZMPOP numkeys instead of the key
BLMPOP, BZMPOP timeout instead of the key
Ring sent them to the wrong shard; cluster recovered through MOVED.
The constructors now set the key position, as XGroup*, ZUnion and FCall
already do. A raw FCALL/FCALL_RO also resolves its key after numkeys,
like EVAL, instead of hashing the function name.
* fix(command): route raw forms by their key
The typed constructors of the commands whose key is not args[1] set the
key position; a raw Do / NewCmd of the same commands does not, so the
shared resolver fell back to args[1]: raw XREAD hashed "streams" and raw
LMPOP its numkeys, which picks the wrong Ring shard, the wrong
full-duplex engine, or a cold cluster slot.
The resolver now finds the key for the raw forms: XREAD and XREADGROUP
after STREAMS (XREADGROUP scans past the group and consumer names),
OBJECT and XINFO (HELP is keyless), BITOP, LMPOP, ZMPOP, SINTERCARD,
ZINTERCARD, BLMPOP, BZMPOP, and MIGRATE, including its KEYS form.
* fix(command): walk MIGRATE options for KEYS
The raw MIGRATE KEYS form was located by scanning for the first "keys"
token after the timeout, so an AUTH or AUTH2 operand that reads "keys"
was taken for the clause and the resolver routed by the token after it.
It now walks the option grammar: COPY and REPLACE take no operand,
AUTH takes one and AUTH2 two, and the key follows KEYS.
199 lines
7.0 KiB
Go
199 lines
7.0 KiB
Go
package redis
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
)
|
|
|
|
type BitMapCmdable interface {
|
|
GetBit(ctx context.Context, key string, offset int64) *IntCmd
|
|
SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd
|
|
BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd
|
|
BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd
|
|
BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd
|
|
BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd
|
|
BitOpDiff(ctx context.Context, destKey string, keys ...string) *IntCmd
|
|
BitOpDiff1(ctx context.Context, destKey string, keys ...string) *IntCmd
|
|
BitOpAndOr(ctx context.Context, destKey string, keys ...string) *IntCmd
|
|
BitOpOne(ctx context.Context, destKey string, keys ...string) *IntCmd
|
|
BitOpNot(ctx context.Context, destKey string, key string) *IntCmd
|
|
BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd
|
|
BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd
|
|
BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd
|
|
BitFieldRO(ctx context.Context, key string, values ...interface{}) *IntSliceCmd
|
|
}
|
|
|
|
func (c cmdable) GetBit(ctx context.Context, key string, offset int64) *IntCmd {
|
|
cmd := NewIntCmd(ctx, "getbit", key, offset)
|
|
_ = c(ctx, cmd)
|
|
return cmd
|
|
}
|
|
|
|
func (c cmdable) SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd {
|
|
cmd := NewIntCmd(
|
|
ctx,
|
|
"setbit",
|
|
key,
|
|
offset,
|
|
value,
|
|
)
|
|
_ = c(ctx, cmd)
|
|
return cmd
|
|
}
|
|
|
|
type BitCount struct {
|
|
Start, End int64
|
|
Unit string // BYTE(default) | BIT
|
|
}
|
|
|
|
const BitCountIndexByte string = "BYTE"
|
|
const BitCountIndexBit string = "BIT"
|
|
|
|
func (c cmdable) BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd {
|
|
args := make([]any, 2, 5)
|
|
args[0] = "bitcount"
|
|
args[1] = key
|
|
if bitCount != nil {
|
|
args = append(args, bitCount.Start, bitCount.End)
|
|
if bitCount.Unit != "" {
|
|
if bitCount.Unit != BitCountIndexByte && bitCount.Unit != BitCountIndexBit {
|
|
cmd := NewIntCmd(ctx)
|
|
cmd.SetErr(errors.New("redis: invalid bitcount index"))
|
|
return cmd
|
|
}
|
|
args = append(args, bitCount.Unit)
|
|
}
|
|
}
|
|
cmd := NewIntCmd(ctx, args...)
|
|
_ = c(ctx, cmd)
|
|
return cmd
|
|
}
|
|
|
|
func (c cmdable) bitOp(ctx context.Context, op, destKey string, keys ...string) *IntCmd {
|
|
args := make([]interface{}, 3+len(keys))
|
|
args[0] = "bitop"
|
|
args[1] = op
|
|
args[2] = destKey
|
|
for i, key := range keys {
|
|
args[3+i] = key
|
|
}
|
|
cmd := NewIntCmd(ctx, args...)
|
|
cmd.SetFirstKeyPos(2) // the key is not args[1]: route by it
|
|
_ = c(ctx, cmd)
|
|
return cmd
|
|
}
|
|
|
|
// BitOpAnd creates a new bitmap in which users are members of all given bitmaps
|
|
func (c cmdable) BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
|
return c.bitOp(ctx, "and", destKey, keys...)
|
|
}
|
|
|
|
// BitOpOr creates a new bitmap in which users are member of at least one given bitmap
|
|
func (c cmdable) BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
|
return c.bitOp(ctx, "or", destKey, keys...)
|
|
}
|
|
|
|
// BitOpXor creates a new bitmap in which users are the result of XORing all given bitmaps
|
|
func (c cmdable) BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
|
return c.bitOp(ctx, "xor", destKey, keys...)
|
|
}
|
|
|
|
// BitOpNot creates a new bitmap in which users are not members of a given bitmap
|
|
func (c cmdable) BitOpNot(ctx context.Context, destKey string, key string) *IntCmd {
|
|
return c.bitOp(ctx, "not", destKey, key)
|
|
}
|
|
|
|
// BitOpDiff creates a new bitmap in which users are members of bitmap X but not of any of bitmaps Y1, Y2, …
|
|
// Introduced with Redis 8.2
|
|
func (c cmdable) BitOpDiff(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
|
return c.bitOp(ctx, "diff", destKey, keys...)
|
|
}
|
|
|
|
// BitOpDiff1 creates a new bitmap in which users are members of one or more of bitmaps Y1, Y2, … but not members of bitmap X
|
|
// Introduced with Redis 8.2
|
|
func (c cmdable) BitOpDiff1(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
|
return c.bitOp(ctx, "diff1", destKey, keys...)
|
|
}
|
|
|
|
// BitOpAndOr creates a new bitmap in which users are members of bitmap X and also members of one or more of bitmaps Y1, Y2, …
|
|
// Introduced with Redis 8.2
|
|
func (c cmdable) BitOpAndOr(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
|
return c.bitOp(ctx, "andor", destKey, keys...)
|
|
}
|
|
|
|
// BitOpOne creates a new bitmap in which users are members of exactly one of the given bitmaps
|
|
// Introduced with Redis 8.2
|
|
func (c cmdable) BitOpOne(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
|
return c.bitOp(ctx, "one", destKey, keys...)
|
|
}
|
|
|
|
// BitPos is an API before Redis version 7.0, cmd: bitpos key bit start end
|
|
// if you need the `byte | bit` parameter, please use `BitPosSpan`.
|
|
func (c cmdable) BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd {
|
|
args := make([]interface{}, 3+len(pos))
|
|
args[0] = "bitpos"
|
|
args[1] = key
|
|
args[2] = bit
|
|
switch len(pos) {
|
|
case 0:
|
|
case 1:
|
|
args[3] = pos[0]
|
|
case 2:
|
|
args[3] = pos[0]
|
|
args[4] = pos[1]
|
|
default:
|
|
cmd := NewIntCmd(ctx)
|
|
cmd.SetErr(errors.New("too many arguments"))
|
|
return cmd
|
|
}
|
|
cmd := NewIntCmd(ctx, args...)
|
|
_ = c(ctx, cmd)
|
|
return cmd
|
|
}
|
|
|
|
// BitPosSpan supports the `byte | bit` parameters in redis version 7.0,
|
|
// the bitpos command defaults to using byte type for the `start-end` range,
|
|
// which means it counts in bytes from start to end. you can set the value
|
|
// of "span" to determine the type of `start-end`.
|
|
// span = "bit", cmd: bitpos key bit start end bit
|
|
// span = "byte", cmd: bitpos key bit start end byte
|
|
func (c cmdable) BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd {
|
|
cmd := NewIntCmd(ctx, "bitpos", key, bit, start, end, span)
|
|
_ = c(ctx, cmd)
|
|
return cmd
|
|
}
|
|
|
|
// BitField accepts multiple values:
|
|
// - BitField("set", "i1", "offset1", "value1","cmd2", "type2", "offset2", "value2")
|
|
// - BitField([]string{"cmd1", "type1", "offset1", "value1","cmd2", "type2", "offset2", "value2"})
|
|
// - BitField([]interface{}{"cmd1", "type1", "offset1", "value1","cmd2", "type2", "offset2", "value2"})
|
|
func (c cmdable) BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd {
|
|
args := make([]interface{}, 2, 2+len(values))
|
|
args[0] = "bitfield"
|
|
args[1] = key
|
|
args = appendArgs(args, values)
|
|
cmd := NewIntSliceCmd(ctx, args...)
|
|
_ = c(ctx, cmd)
|
|
return cmd
|
|
}
|
|
|
|
// BitFieldRO - Read-only variant of the BITFIELD command.
|
|
// It is like the original BITFIELD but only accepts GET subcommand and can safely be used in read-only replicas.
|
|
// - BitFieldRO(ctx, key, "<Encoding0>", "<Offset0>", "<Encoding1>","<Offset1>")
|
|
func (c cmdable) BitFieldRO(ctx context.Context, key string, values ...interface{}) *IntSliceCmd {
|
|
args := make([]interface{}, 2, 2+len(values))
|
|
args[0] = "BITFIELD_RO"
|
|
args[1] = key
|
|
if len(values)%2 != 0 {
|
|
c := NewIntSliceCmd(ctx)
|
|
c.SetErr(errors.New("BitFieldRO: invalid number of arguments, must be even"))
|
|
return c
|
|
}
|
|
for i := 0; i < len(values); i += 2 {
|
|
args = append(args, "GET", values[i], values[i+1])
|
|
}
|
|
cmd := NewIntSliceCmd(ctx, args...)
|
|
_ = c(ctx, cmd)
|
|
return cmd
|
|
}
|