mirror of https://github.com/ledisdb/ledisdb.git
merge branch 'bin-feature'
This commit is contained in:
parent
fd248aaa35
commit
febe8ee93d
173
ledis/t_bin.go
173
ledis/t_bin.go
|
@ -100,20 +100,20 @@ func setBit(sz []byte, offset uint32, val uint8) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (datas *segBitInfoArray) Len() int {
|
func (datas segBitInfoArray) Len() int {
|
||||||
return len(*datas)
|
return len(datas)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (datas *segBitInfoArray) Less(i, j int) bool {
|
func (datas segBitInfoArray) Less(i, j int) bool {
|
||||||
res := (*datas)[i].Seq < (*datas)[j].Seq
|
res := (datas)[i].Seq < (datas)[j].Seq
|
||||||
if !res && (*datas)[i].Seq == (*datas)[j].Seq {
|
if !res && (datas)[i].Seq == (datas)[j].Seq {
|
||||||
res = (*datas)[i].Off < (*datas)[j].Off
|
res = (datas)[i].Off < (datas)[j].Off
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (datas *segBitInfoArray) Swap(i, j int) {
|
func (datas segBitInfoArray) Swap(i, j int) {
|
||||||
(*datas)[i], (*datas)[j] = (*datas)[j], (*datas)[i]
|
datas[i], datas[j] = datas[j], datas[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) bEncodeMetaKey(key []byte) []byte {
|
func (db *DB) bEncodeMetaKey(key []byte) []byte {
|
||||||
|
@ -214,10 +214,6 @@ func (db *DB) bGetMeta(key []byte) (tailSeq int32, tailOff int32, err error) {
|
||||||
func (db *DB) bSetMeta(t *tx, key []byte, tailSeq uint32, tailOff uint32) {
|
func (db *DB) bSetMeta(t *tx, key []byte, tailSeq uint32, tailOff uint32) {
|
||||||
ek := db.bEncodeMetaKey(key)
|
ek := db.bEncodeMetaKey(key)
|
||||||
|
|
||||||
// todo ..
|
|
||||||
// if size == 0 // headSeq == tailSeq
|
|
||||||
// t.Delete(ek)
|
|
||||||
|
|
||||||
buf := make([]byte, 8)
|
buf := make([]byte, 8)
|
||||||
binary.LittleEndian.PutUint32(buf[0:4], tailSeq)
|
binary.LittleEndian.PutUint32(buf[0:4], tailSeq)
|
||||||
binary.LittleEndian.PutUint32(buf[4:8], tailOff)
|
binary.LittleEndian.PutUint32(buf[4:8], tailOff)
|
||||||
|
@ -453,19 +449,18 @@ func (db *DB) BSetBit(key []byte, offset int32, val uint8) (ori uint8, err error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) BMSetBit(key []byte, args ...BitPair) (place int32, err error) {
|
func (db *DB) BMSetBit(key []byte, args ...BitPair) (place int64, err error) {
|
||||||
if err = checkKeySize(key); err != nil {
|
if err = checkKeySize(key); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// (ps : so as to aviod wasting the mem copy while calling db.Get() and batch.Put(),
|
// (ps : so as to aviod wasting memory copy while calling db.Get() and batch.Put(),
|
||||||
// here we sequence the request pos in order, so that we can execute diff pos
|
// here we sequence the params by pos, so that we can merge the execution of
|
||||||
// set on the same segment by justing calling db.Get() and batch.Put() one time
|
// diff pos setting which targets on the same segment respectively. )
|
||||||
// either.)
|
|
||||||
|
|
||||||
// sort the requesting data by set pos
|
// #1 : sequence request data
|
||||||
var argCnt = len(args)
|
var argCnt = len(args)
|
||||||
var bInfos segBitInfoArray = make(segBitInfoArray, argCnt)
|
var bitInfos segBitInfoArray = make(segBitInfoArray, argCnt)
|
||||||
var seq, off uint32
|
var seq, off uint32
|
||||||
|
|
||||||
for i, info := range args {
|
for i, info := range args {
|
||||||
|
@ -473,54 +468,53 @@ func (db *DB) BMSetBit(key []byte, args ...BitPair) (place int32, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bInfos[i].Seq = seq
|
bitInfos[i].Seq = seq
|
||||||
bInfos[i].Off = off
|
bitInfos[i].Off = off
|
||||||
bInfos[i].Val = info.Val
|
bitInfos[i].Val = info.Val
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(&bInfos)
|
sort.Sort(bitInfos)
|
||||||
|
|
||||||
for i := 1; i < argCnt; i++ {
|
for i := 1; i < argCnt; i++ {
|
||||||
if bInfos[i].Seq == bInfos[i-1].Seq && bInfos[i].Off == bInfos[i-1].Off {
|
if bitInfos[i].Seq == bitInfos[i-1].Seq && bitInfos[i].Off == bitInfos[i-1].Off {
|
||||||
return 0, errDuplicatePos
|
return 0, errDuplicatePos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set data
|
// #2 : execute bit set in order
|
||||||
t := db.binTx
|
t := db.binTx
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
|
|
||||||
var bk, segment []byte
|
var curBinKey, curSeg []byte
|
||||||
var lastSeq, maxSeq, maxOff uint32
|
var curSeq, maxSeq, maxOff uint32
|
||||||
|
|
||||||
for _, info := range bInfos {
|
for _, info := range bitInfos {
|
||||||
if segment != nil && info.Seq != lastSeq {
|
if curSeg != nil && info.Seq != curSeq {
|
||||||
t.Put(bk, segment)
|
t.Put(curBinKey, curSeg)
|
||||||
segment = nil
|
curSeg = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if segment == nil {
|
if curSeg == nil {
|
||||||
if bk, segment, err = db.bAllocateSegment(key, info.Seq); err != nil {
|
curSeq = info.Seq
|
||||||
|
if curBinKey, curSeg, err = db.bAllocateSegment(key, info.Seq); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if segment == nil {
|
if curSeg == nil {
|
||||||
continue
|
continue
|
||||||
} else {
|
|
||||||
lastSeq = info.Seq
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if setBit(segment, info.Off, info.Val) {
|
if setBit(curSeg, info.Off, info.Val) {
|
||||||
maxSeq = info.Seq
|
maxSeq = info.Seq
|
||||||
maxOff = info.Off
|
maxOff = info.Off
|
||||||
place++
|
place++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if segment != nil {
|
if curSeg != nil {
|
||||||
t.Put(bk, segment)
|
t.Put(curBinKey, curSeg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally, update meta
|
// finally, update meta
|
||||||
|
@ -601,10 +595,9 @@ func (db *DB) BTail(key []byte) (int32, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32, err error) {
|
func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32, err error) {
|
||||||
// return :
|
// blen -
|
||||||
// The size of the string stored in the destination key,
|
// the size of the string stored in the destination key,
|
||||||
// that is equal to the size of the longest input string.
|
// that is equal to the size of the longest input string.
|
||||||
blen = -1
|
|
||||||
|
|
||||||
var exeOp func([]byte, []byte, *[]byte)
|
var exeOp func([]byte, []byte, *[]byte)
|
||||||
switch op {
|
switch op {
|
||||||
|
@ -622,58 +615,71 @@ func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (op == OPnot && len(srckeys) != 1) ||
|
|
||||||
(op != OPnot && len(srckeys) < 2) {
|
|
||||||
// msg("lack of arguments")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t := db.binTx
|
t := db.binTx
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
|
|
||||||
// init - meta info
|
var srcKseq, srcKoff int32
|
||||||
var maxDstSeq, maxDstOff uint32
|
var seq, off, maxDstSeq, maxDstOff uint32
|
||||||
var nowSeq, nowOff int32
|
|
||||||
|
|
||||||
var keyNum int = len(srckeys)
|
var keyNum int = len(srckeys)
|
||||||
var srcIdx int = 0
|
var validKeyNum int
|
||||||
for ; srcIdx < keyNum; srcIdx++ {
|
for i := 0; i < keyNum; i++ {
|
||||||
if nowSeq, nowOff, err = db.bGetMeta(srckeys[srcIdx]); err != nil { // todo : if key not exists ....
|
if srcKseq, srcKoff, err = db.bGetMeta(srckeys[i]); err != nil {
|
||||||
return
|
return
|
||||||
|
} else if srcKseq < 0 {
|
||||||
|
srckeys[i] = nil
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if nowSeq >= 0 {
|
validKeyNum++
|
||||||
maxDstSeq = uint32(nowSeq)
|
|
||||||
maxDstOff = uint32(nowOff)
|
seq = uint32(srcKseq)
|
||||||
|
off = uint32(srcKoff)
|
||||||
|
if seq > maxDstSeq || (seq == maxDstSeq && off > maxDstOff) {
|
||||||
|
maxDstSeq = seq
|
||||||
|
maxDstOff = off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op == OPnot && validKeyNum != 1) ||
|
||||||
|
(op != OPnot && validKeyNum < 2) {
|
||||||
|
return // with not enough existing source key
|
||||||
|
}
|
||||||
|
|
||||||
|
var srcIdx int
|
||||||
|
for srcIdx = 0; srcIdx < keyNum; srcIdx++ {
|
||||||
|
if srckeys[srcIdx] != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if srcIdx == keyNum {
|
|
||||||
// none existing src key
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// init - data
|
// init - data
|
||||||
var seq, off uint32
|
var segments = make([][]byte, maxDstSeq+1)
|
||||||
var segments = make([][]byte, maxSegCount) // todo : init count also can be optimize while 'and' / 'or'
|
|
||||||
|
|
||||||
if op == OPnot {
|
if op == OPnot {
|
||||||
|
// ps :
|
||||||
|
// ( ~num == num ^ 0x11111111 )
|
||||||
|
// we init the result segments with all bit set,
|
||||||
|
// then we can calculate through the way of 'xor'.
|
||||||
|
|
||||||
|
// ahead segments bin format : 1111 ... 1111
|
||||||
for i := uint32(0); i < maxDstSeq; i++ {
|
for i := uint32(0); i < maxDstSeq; i++ {
|
||||||
segments[i] = fillSegment
|
segments[i] = fillSegment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// last segment bin format : 1111..1100..0000
|
||||||
var tailSeg = make([]byte, segByteSize, segByteSize)
|
var tailSeg = make([]byte, segByteSize, segByteSize)
|
||||||
var fillByte = fillBits[7]
|
var fillByte = fillBits[7]
|
||||||
var cnt = db.bCapByteSize(uint32(0), maxDstOff)
|
var tailSegLen = db.bCapByteSize(uint32(0), maxDstOff)
|
||||||
for i := uint32(0); i < cnt-1; i++ {
|
for i := uint32(0); i < tailSegLen-1; i++ {
|
||||||
tailSeg[i] = fillByte
|
tailSeg[i] = fillByte
|
||||||
}
|
}
|
||||||
tailSeg[cnt-1] = fillBits[maxDstOff-(cnt-1)<<3]
|
tailSeg[tailSegLen-1] = fillBits[maxDstOff-(tailSegLen-1)<<3]
|
||||||
segments[maxDstSeq] = tailSeg
|
segments[maxDstSeq] = tailSeg
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
// ps : init segments by data corresponding to the 1st valid source key
|
||||||
it := db.bIterator(srckeys[srcIdx])
|
it := db.bIterator(srckeys[srcIdx])
|
||||||
for ; it.Valid(); it.Next() {
|
for ; it.Valid(); it.Next() {
|
||||||
if _, seq, err = db.bDecodeBinKey(it.Key()); err != nil {
|
if _, seq, err = db.bDecodeBinKey(it.Key()); err != nil {
|
||||||
|
@ -689,21 +695,9 @@ func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32
|
||||||
|
|
||||||
// operation with following keys
|
// operation with following keys
|
||||||
var res []byte
|
var res []byte
|
||||||
|
|
||||||
for i := srcIdx; i < keyNum; i++ {
|
for i := srcIdx; i < keyNum; i++ {
|
||||||
if nowSeq, nowOff, err = db.bGetMeta(srckeys[i]); err != nil {
|
if srckeys[i] == nil {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if nowSeq < 0 {
|
|
||||||
continue
|
continue
|
||||||
} else {
|
|
||||||
seq = uint32(nowSeq)
|
|
||||||
off = uint32(nowOff)
|
|
||||||
if seq > maxDstSeq || (seq == maxDstSeq && off > maxDstOff) {
|
|
||||||
maxDstSeq = seq
|
|
||||||
maxDstOff = off
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it := db.bIterator(srckeys[i])
|
it := db.bIterator(srckeys[i])
|
||||||
|
@ -716,13 +710,14 @@ func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
seq = maxSegCount
|
seq = maxDstSeq + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo :
|
// todo :
|
||||||
// operation 'and' can be optimize here :
|
// operation 'and' can be optimize here :
|
||||||
// if seq > max_segments_idx, this loop can be break,
|
// if seq > max_segments_idx, this loop can be break,
|
||||||
// which can avoid cost from Key() and decode key
|
// which can avoid cost from Key() and bDecodeBinKey()
|
||||||
|
|
||||||
for ; idx < seq; idx++ {
|
for ; idx < seq; idx++ {
|
||||||
res = nil
|
res = nil
|
||||||
exeOp(segments[idx], nil, &res)
|
exeOp(segments[idx], nil, &res)
|
||||||
|
@ -743,23 +738,19 @@ func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32
|
||||||
db.bDelete(t, dstkey)
|
db.bDelete(t, dstkey)
|
||||||
db.rmExpire(t, bExpType, dstkey)
|
db.rmExpire(t, bExpType, dstkey)
|
||||||
|
|
||||||
// set data and meta
|
// set data
|
||||||
db.bSetMeta(t, dstkey, maxDstSeq, maxDstOff)
|
db.bSetMeta(t, dstkey, maxDstSeq, maxDstOff)
|
||||||
|
|
||||||
// set data
|
|
||||||
var bk []byte
|
var bk []byte
|
||||||
for seq, seg := range segments {
|
for seq, segt := range segments {
|
||||||
if seg != nil {
|
if segt != nil {
|
||||||
// todo:
|
|
||||||
// here can be optimize, like 'updateBinKeySeq',
|
|
||||||
// avoid allocate too many mem
|
|
||||||
bk = db.bEncodeBinKey(dstkey, uint32(seq))
|
bk = db.bEncodeBinKey(dstkey, uint32(seq))
|
||||||
t.Put(bk, seg)
|
t.Put(bk, segt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.Commit()
|
err = t.Commit()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
blen = int32(maxDstSeq<<segBitWidth | maxDstOff)
|
blen = int32(maxDstSeq<<segBitWidth | maxDstOff)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -262,17 +262,21 @@ func testOpXor(t *testing.T) {
|
||||||
|
|
||||||
dstKey := []byte("test_bin_op_xor")
|
dstKey := []byte("test_bin_op_xor")
|
||||||
|
|
||||||
k0 := []byte("op_2_00")
|
k0 := []byte("op_xor_00")
|
||||||
k1 := []byte("op_2_01")
|
k1 := []byte("op_xor_01")
|
||||||
srcKeys := [][]byte{k0, k1}
|
srcKeys := [][]byte{k0, k1}
|
||||||
|
|
||||||
db.BSetBit(k0, int32(0), 1)
|
reqs := make([]BitPair, 4)
|
||||||
db.BSetBit(k0, int32(7), 1)
|
reqs[0] = BitPair{0, 1}
|
||||||
db.BSetBit(k0, int32(segBitSize-1), 1)
|
reqs[1] = BitPair{7, 1}
|
||||||
db.BSetBit(k0, int32(segBitSize-8), 1)
|
reqs[2] = BitPair{int32(segBitSize - 1), 1}
|
||||||
|
reqs[3] = BitPair{int32(segBitSize - 8), 1}
|
||||||
|
db.BMSetBit(k0, reqs...)
|
||||||
|
|
||||||
db.BSetBit(k1, int32(7), 1)
|
reqs = make([]BitPair, 2)
|
||||||
db.BSetBit(k1, int32(segBitSize-8), 1)
|
reqs[0] = BitPair{7, 1}
|
||||||
|
reqs[1] = BitPair{int32(segBitSize - 8), 1}
|
||||||
|
db.BMSetBit(k1, reqs...)
|
||||||
|
|
||||||
var stdData []byte
|
var stdData []byte
|
||||||
var data []byte
|
var data []byte
|
||||||
|
@ -295,7 +299,7 @@ func testOpNot(t *testing.T) {
|
||||||
db.FlushAll()
|
db.FlushAll()
|
||||||
|
|
||||||
// intputs
|
// intputs
|
||||||
dstKey := []byte("test_bin_op_xor")
|
dstKey := []byte("test_bin_op_not")
|
||||||
|
|
||||||
k0 := []byte("op_not_0")
|
k0 := []byte("op_not_0")
|
||||||
srcKeys := [][]byte{k0}
|
srcKeys := [][]byte{k0}
|
||||||
|
|
|
@ -11,7 +11,8 @@ var mapExpMetaType = map[byte]byte{
|
||||||
kvExpType: kvExpMetaType,
|
kvExpType: kvExpMetaType,
|
||||||
lExpType: lExpMetaType,
|
lExpType: lExpMetaType,
|
||||||
hExpType: hExpMetaType,
|
hExpType: hExpMetaType,
|
||||||
zExpType: zExpMetaType}
|
zExpType: zExpMetaType,
|
||||||
|
bExpType: bExpMetaType}
|
||||||
|
|
||||||
type retireCallback func(*tx, []byte) int64
|
type retireCallback func(*tx, []byte) int64
|
||||||
|
|
||||||
|
@ -138,6 +139,9 @@ func newEliminator(db *DB) *elimination {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (eli *elimination) regRetireContext(expType byte, t *tx, onRetire retireCallback) {
|
func (eli *elimination) regRetireContext(expType byte, t *tx, onRetire retireCallback) {
|
||||||
|
|
||||||
|
// todo .. need to ensure exist - mapExpMetaType[expType]
|
||||||
|
|
||||||
eli.exp2Tx[expType] = t
|
eli.exp2Tx[expType] = t
|
||||||
eli.exp2Retire[expType] = onRetire
|
eli.exp2Retire[expType] = onRetire
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,28 @@ func StrInt64(v []byte, err error) (int64, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StrInt32(v []byte, err error) (int32, error) {
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if v == nil {
|
||||||
|
return 0, nil
|
||||||
|
} else {
|
||||||
|
res, err := strconv.ParseInt(String(v), 10, 32)
|
||||||
|
return int32(res), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StrInt8(v []byte, err error) (int8, error) {
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if v == nil {
|
||||||
|
return 0, nil
|
||||||
|
} else {
|
||||||
|
res, err := strconv.ParseInt(String(v), 10, 32)
|
||||||
|
return int8(res), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func StrPutInt64(v int64) []byte {
|
func StrPutInt64(v int64) []byte {
|
||||||
return strconv.AppendInt(nil, v, 10)
|
return strconv.AppendInt(nil, v, 10)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,273 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/siddontang/ledisdb/ledis"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func bgetCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) != 1 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := c.db.BGet(args[0]); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeBulk(v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bdeleteCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) != 1 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, err := c.db.BDelete(args[0]); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeInteger(n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bsetbitCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) != 3 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var offset int32
|
||||||
|
var val int8
|
||||||
|
|
||||||
|
offset, err = ledis.StrInt32(args[1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err = ledis.StrInt8(args[2], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ori, err := c.db.BSetBit(args[0], offset, uint8(val)); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeInteger(int64(ori))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bgetbitCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) != 2 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
offset, err := ledis.StrInt32(args[1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := c.db.BGetBit(args[0], offset); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeInteger(int64(v))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bmsetbitCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) < 3 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
key := args[0]
|
||||||
|
if len(args[1:])&1 != 0 {
|
||||||
|
return ErrCmdParams
|
||||||
|
} else {
|
||||||
|
args = args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var offset int32
|
||||||
|
var val int8
|
||||||
|
|
||||||
|
pairs := make([]ledis.BitPair, len(args)>>1)
|
||||||
|
for i := 0; i < len(pairs); i++ {
|
||||||
|
offset, err = ledis.StrInt32(args[i<<1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err = ledis.StrInt8(args[i<<1+1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs[i].Pos = offset
|
||||||
|
pairs[i].Val = uint8(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if place, err := c.db.BMSetBit(key, pairs...); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeInteger(place)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bcountCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
argCnt := len(args)
|
||||||
|
|
||||||
|
if !(argCnt > 0 && argCnt <= 3) {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// BCount(key []byte, start int32, end int32) (cnt int32, err error) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var start, end int32 = 0, -1
|
||||||
|
|
||||||
|
if argCnt > 1 {
|
||||||
|
start, err = ledis.StrInt32(args[1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if argCnt > 2 {
|
||||||
|
end, err = ledis.StrInt32(args[2], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt, err := c.db.BCount(args[0], start, end); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeInteger(int64(cnt))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func boptCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) < 2 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
opDesc := strings.ToLower(ledis.String(args[0]))
|
||||||
|
dstKey := args[1]
|
||||||
|
srcKeys := args[2:]
|
||||||
|
|
||||||
|
var op uint8
|
||||||
|
switch opDesc {
|
||||||
|
case "and":
|
||||||
|
op = ledis.OPand
|
||||||
|
case "or":
|
||||||
|
op = ledis.OPor
|
||||||
|
case "xor":
|
||||||
|
op = ledis.OPxor
|
||||||
|
case "not":
|
||||||
|
op = ledis.OPnot
|
||||||
|
default:
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
if blen, err := c.db.BOperation(op, dstKey, srcKeys...); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeInteger(int64(blen))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bexpireCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) == 0 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
duration, err := ledis.StrInt64(args[1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := c.db.BExpire(args[0], duration); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeInteger(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bexpireatCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) == 0 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
when, err := ledis.StrInt64(args[1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := c.db.BExpireAt(args[0], when); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeInteger(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bttlCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) == 0 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := c.db.BTTL(args[0]); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeInteger(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bpersistCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) != 1 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, err := c.db.BPersist(args[0]); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.writeInteger(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("bget", bgetCommand)
|
||||||
|
register("bdelete", bdeleteCommand)
|
||||||
|
register("bsetbit", bsetbitCommand)
|
||||||
|
register("bgetbit", bgetbitCommand)
|
||||||
|
register("bmsetbit", bmsetbitCommand)
|
||||||
|
register("bcount", bcountCommand)
|
||||||
|
register("bopt", boptCommand)
|
||||||
|
register("bexpire", bexpireCommand)
|
||||||
|
register("bexpireat", bexpireatCommand)
|
||||||
|
register("bttl", bttlCommand)
|
||||||
|
register("bpersist", bpersistCommand)
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/siddontang/ledisdb/client/go/ledis"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBin(t *testing.T) {
|
||||||
|
testBinGetSet(t)
|
||||||
|
testBinMset(t)
|
||||||
|
testBinCount(t)
|
||||||
|
testBinOpt(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBinGetSet(t *testing.T) {
|
||||||
|
c := getTestConn()
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
key := []byte("test_cmd_bin_basic")
|
||||||
|
|
||||||
|
// get / set
|
||||||
|
if v, err := ledis.Int(c.Do("bgetbit", key, 1024)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if v != 0 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ori, err := ledis.Int(c.Do("bsetbit", key, 1024, 1)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if ori != 0 {
|
||||||
|
t.Fatal(ori)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := ledis.Int(c.Do("bgetbit", key, 1024)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if v != 1 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch from revert pos
|
||||||
|
c.Do("bsetbit", key, 1000, 1)
|
||||||
|
|
||||||
|
if v, err := ledis.Int(c.Do("bgetbit", key, -1)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if v != 1 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := ledis.Int(c.Do("bgetbit", key, -25)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if v != 1 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete
|
||||||
|
if drop, err := ledis.Int(c.Do("bdelete", key)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if drop != 1 {
|
||||||
|
t.Fatal(drop)
|
||||||
|
}
|
||||||
|
|
||||||
|
if drop, err := ledis.Int(c.Do("bdelete", key)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if drop != 0 {
|
||||||
|
t.Fatal(drop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBinMset(t *testing.T) {
|
||||||
|
c := getTestConn()
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
key := []byte("test_cmd_bin_mset")
|
||||||
|
|
||||||
|
if n, err := ledis.Int(
|
||||||
|
c.Do("bmsetbit", key,
|
||||||
|
500, 0,
|
||||||
|
100, 1,
|
||||||
|
200, 1,
|
||||||
|
1000, 1,
|
||||||
|
900, 0,
|
||||||
|
500000, 1,
|
||||||
|
600, 0,
|
||||||
|
300, 1,
|
||||||
|
100000, 1)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if n != 9 {
|
||||||
|
t.Fatal(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fillPos := []int{100, 200, 300, 1000, 100000, 500000}
|
||||||
|
for _, pos := range fillPos {
|
||||||
|
v, err := ledis.Int(c.Do("bgetbit", key, pos))
|
||||||
|
if err != nil || v != 1 {
|
||||||
|
t.Fatal(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// err
|
||||||
|
if n, err := ledis.Int(
|
||||||
|
c.Do("bmsetbit", key, 3, 0, 2, 1, 3, 0, 1, 1)); err == nil || n != 0 {
|
||||||
|
t.Fatal(n) // duplication on pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBinCount(t *testing.T) {
|
||||||
|
c := getTestConn()
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
key := []byte("test_cmd_bin_count")
|
||||||
|
sum := 0
|
||||||
|
for pos := 1; pos < 1000000; pos += 10001 {
|
||||||
|
c.Do("bsetbit", key, pos, 1)
|
||||||
|
sum++
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, err := ledis.Int(c.Do("bcount", key)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if n != sum {
|
||||||
|
t.Fatal(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBinOpt(t *testing.T) {
|
||||||
|
c := getTestConn()
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
dstk := []byte("bin_op_res")
|
||||||
|
kmiss := []byte("bin_op_miss")
|
||||||
|
|
||||||
|
k0 := []byte("bin_op_0")
|
||||||
|
k1 := []byte("bin_op_1")
|
||||||
|
c.Do("bmsetbit", k0, 10, 1, 30, 1, 50, 1, 70, 1, 100, 1)
|
||||||
|
c.Do("bmsetbit", k1, 20, 1, 40, 1, 60, 1, 80, 1, 100, 1)
|
||||||
|
|
||||||
|
// case - lack of args
|
||||||
|
// todo ...
|
||||||
|
|
||||||
|
// case - 'not' on inexisting key
|
||||||
|
if blen, err := ledis.Int(
|
||||||
|
c.Do("bopt", "not", dstk, kmiss)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if blen != 0 {
|
||||||
|
t.Fatal(blen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := ledis.String(c.Do("bget", dstk)); v != "" {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// case - 'and', 'or', 'xor' with inexisting key
|
||||||
|
opts := []string{"and", "or", "xor"}
|
||||||
|
for _, op := range opts {
|
||||||
|
if blen, err := ledis.Int(
|
||||||
|
c.Do("bopt", op, dstk, kmiss, k0)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if blen != 0 {
|
||||||
|
t.Fatal(blen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// case - 'and'
|
||||||
|
if blen, err := ledis.Int(
|
||||||
|
c.Do("bopt", "and", dstk, k0, k1)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if blen != 100 {
|
||||||
|
t.Fatal(blen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := ledis.Int(c.Do("bgetbit", dstk, 100)); v != 1 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := ledis.Int(c.Do("bgetbit", dstk, 20)); v != 0 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := ledis.Int(c.Do("bgetbit", dstk, 40)); v != 0 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// case - 'or'
|
||||||
|
if blen, err := ledis.Int(
|
||||||
|
c.Do("bopt", "or", dstk, k0, k1)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if blen != 100 {
|
||||||
|
t.Fatal(blen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := ledis.Int(c.Do("bgetbit", dstk, 100)); v != 1 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := ledis.Int(c.Do("bgetbit", dstk, 20)); v != 1 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := ledis.Int(c.Do("bgetbit", dstk, 40)); v != 1 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// case - 'xor'
|
||||||
|
if blen, err := ledis.Int(
|
||||||
|
c.Do("bopt", "xor", dstk, k0, k1)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if blen != 100 {
|
||||||
|
t.Fatal(blen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := ledis.Int(c.Do("bgetbit", dstk, 100)); v != 0 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := ledis.Int(c.Do("bgetbit", dstk, 20)); v != 1 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := ledis.Int(c.Do("bgetbit", dstk, 40)); v != 1 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
Loading…
Reference in New Issue