diff --git a/ledis/const.go b/ledis/const.go index 577464d..024e38c 100644 --- a/ledis/const.go +++ b/ledis/const.go @@ -22,6 +22,11 @@ const ( hExpMetaType zExpType zExpMetaType + + binType + binMetaType + bExpType + bExpMetaType ) const ( diff --git a/ledis/ledis.go b/ledis/ledis.go index 6bf333a..b9731c2 100644 --- a/ledis/ledis.go +++ b/ledis/ledis.go @@ -33,6 +33,7 @@ type DB struct { listTx *tx hashTx *tx zsetTx *tx + binTx *tx } type Ledis struct { @@ -114,6 +115,7 @@ func newDB(l *Ledis, index uint8) *DB { d.listTx = newTx(l) d.hashTx = newTx(l) d.zsetTx = newTx(l) + d.binTx = newTx(l) return d } diff --git a/ledis/t_bin.go b/ledis/t_bin.go new file mode 100644 index 0000000..044f6cf --- /dev/null +++ b/ledis/t_bin.go @@ -0,0 +1,342 @@ +package ledis + +import ( + "encoding/binary" + "errors" + "github.com/siddontang/ledisdb/leveldb" +) + +const ( + OPand byte = iota + 1 + OPor + OPxor + OPnot +) + +const ( + segWidth uint32 = 9 + segSize uint32 = uint32(1 << segWidth) // byte + + minSeq uint32 = 0 + maxSeq uint32 = uint32((1 << 31) - 1) +) + +var bitsInByte = [256]int32{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8} + +var errBinKey = errors.New("invalid bin key") +var errOffset = errors.New("invalid offset") + +func getBit(sz []byte, offset uint32) uint8 { + index := offset >> 3 + if index >= uint32(len(sz)) { + return 0 // error("overflow") + } + + offset -= index << 3 + return sz[index] >> offset & 1 +} + +func setBit(sz []byte, offset uint32, val uint8) bool { + if val != 1 && val != 0 { + return false // error("invalid val") + } + + index := offset >> 3 + if index >= uint32(len(sz)) { + return false // error("overflow") + } + + offset -= index << 3 + if sz[index]>>offset&1 != val { + sz[index] ^= (1 << offset) + } + return true +} + +func (db *DB) bEncodeMetaKey(key []byte) []byte { + mk := make([]byte, len(key)+2) + mk[0] = db.index + mk[1] = binMetaType + + copy(mk, key) + return mk +} + +func (db *DB) bEncodeBinKey(key []byte, seq uint32) []byte { + bk := make([]byte, len(key)+8) + + pos := 0 + bk[pos] = db.index + pos++ + bk[pos] = binType + + binary.BigEndian.PutUint16(bk[pos:], uint16(len(key))) + pos += 2 + + copy(bk[pos:], key) + pos += len(key) + + binary.BigEndian.PutUint32(bk[pos:], seq) + + return bk +} + +func (db *DB) bDecodeBinKey(bkey []byte) (key []byte, seq uint32, err error) { + if len(bkey) < 8 || bkey[0] != db.index { + err = errBinKey + return + } + + keyLen := binary.BigEndian.Uint16(bkey[2:4]) + if int(keyLen+8) != len(bkey) { + err = errBinKey + return + } + + key = bkey[4 : 4+keyLen] + seq = uint32(binary.BigEndian.Uint16(bkey[4+keyLen:])) + return +} + +func (db *DB) bParseOffset(key []byte, offset int32) (seq uint32, off uint32, err error) { + if offset < 0 { + if tailSeq, tailOff, e := db.bGetMeta(key); e != nil { + err = e + return + } else { + offset += int32(tailSeq<> segWidth + off &= (segSize - 1) + return +} + +func (db *DB) bGetMeta(key []byte) (tailSeq uint32, tailOff uint32, err error) { + var v []byte + + mk := db.bEncodeMetaKey(key) + v, err = db.db.Get(mk) + if err != nil { + return + } + + if v != nil { + tailSeq = binary.LittleEndian.Uint32(v[0:4]) + tailOff = binary.LittleEndian.Uint32(v[4:8]) + } + return +} + +func (db *DB) bSetMeta(t *tx, key []byte, tailSeq uint32, tailOff uint32) { + ek := db.bEncodeMetaKey(key) + + // todo .. + // if size == 0 // headSeq == tailSeq + // t.Delete(ek) + + buf := make([]byte, 8) + binary.LittleEndian.PutUint32(buf[0:4], tailSeq) + binary.LittleEndian.PutUint32(buf[4:8], tailOff) + + t.Put(ek, buf) + return +} + +func (db *DB) bUpdateMeta(t *tx, key []byte, seq uint32, off uint32) (tailSeq uint32, tailOff uint32, err error) { + if tailSeq, tailOff, err = db.bGetMeta(key); err != nil { + return + } + + if seq > tailSeq || (seq == tailSeq && off > tailOff) { + db.bSetMeta(t, key, seq, off) + tailSeq = seq + tailOff = off + } + return +} + +// func (db *DB) bDelete(key []byte) int64 { +// return 0 +// } + +func (db *DB) BGet(key []byte) (data []byte, err error) { + if err = checkKeySize(key); err != nil { + return + } + + var tailSeq, tailOff uint32 + if tailSeq, tailOff, err = db.bGetMeta(key); err != nil { + return + } + + var offByteLen uint32 = tailOff >> 3 + if tailOff&7 > 0 { + offByteLen++ + } + + var dataCap uint32 = tailSeq<<3 + offByteLen + data = make([]byte, dataCap) + + minKey := db.bEncodeBinKey(key, minSeq) + maxKey := db.bEncodeBinKey(key, tailSeq) + it := db.db.RangeLimitIterator(minKey, maxKey, leveldb.RangeClose, 0, -1) + for pos, end := uint32(0), uint32(0); it.Valid(); it.Next() { + end = pos + segSize + if end >= offByteLen { + end = offByteLen + } + + copy(data[pos:end], it.Value()) + pos = end + } + it.Close() + + return +} + +// func (db *DB) BDelete(key []byte) (int8, error) { + +// } + +func (db *DB) getSegment(key []byte, seq uint32) ([]byte, []byte, error) { + bk := db.bEncodeBinKey(key, seq) + segment, err := db.db.Get(bk) + if err != nil { + return bk, nil, err + } + return bk, segment, nil +} + +func (db *DB) allocateSegment(key []byte, seq uint32) ([]byte, []byte, error) { + bk, segment, err := db.getSegment(key, seq) + if err == nil && segment == nil { + segment = make([]byte, segSize, segSize) // can be optimize ? + } + return bk, segment, err +} + +func (db *DB) BSetBit(key []byte, offset int32, val uint8) (ori uint8, err error) { + if err = checkKeySize(key); err != nil { + return + } + + // todo : check offset + var seq, off uint32 + if seq, off, err = db.bParseOffset(key, offset); err != nil { + return 0, err + } + + var bk, segment []byte + if bk, segment, err = db.allocateSegment(key, seq); err != nil { + return 0, err + } + + if segment != nil { + ori = getBit(segment, off) + setBit(segment, off, val) + + t := db.binTx + t.Lock() + t.Put(bk, segment) + if _, _, e := db.bUpdateMeta(t, key, seq, off); e != nil { + err = e + return + } + err = t.Commit() + t.Unlock() + } + + return +} + +func (db *DB) BGetBit(key []byte, offset int32) (uint8, error) { + if seq, off, err := db.bParseOffset(key, offset); err != nil { + return 0, err + } else { + _, segment, err := db.getSegment(key, seq) + if err != nil { + return 0, err + } + + if segment == nil { + return 0, nil + } else { + return getBit(segment, off), nil + } + } +} + +// func (db *DB) BGetRange(key []byte, start int32, end int32) ([]byte, error) { +// section := make([]byte) + +// return +// } + +func (db *DB) BCount(key []byte, start int32, end int32) (cnt int32, err error) { + var sseq uint32 + if sseq, _, err = db.bParseOffset(key, start); err != nil { + return + } + + var eseq uint32 + if eseq, _, err = db.bParseOffset(key, end); err != nil { + return + } + + var segment []byte + skey := db.bEncodeBinKey(key, sseq) + ekey := db.bEncodeBinKey(key, eseq) + + it := db.db.RangeLimitIterator(skey, ekey, leveldb.RangeClose, 0, -1) + for ; it.Valid(); it.Next() { + segment = it.Value() + for _, bit := range segment { + cnt += bitsInByte[bit] + } + } + it.Close() + + return +} + +// func (db *DB) BLen(key []) (uint32, error) { + +// } + +// func (db *DB) BOperation(op byte, dstkey []byte, srckeys ...[]byte) (int32, error) { +// // return : +// // The size of the string stored in the destination key, +// // that is equal to the size of the longest input string. +// if op < OPand || op > OPnot { +// return +// } + +// } + +// func (db *DB) BExpire(key []byte, duration int64) (int64, error) { + +// } + +// func (db *DB) BExpireAt(key []byte, when int64) (int64, error) { + +// } + +// func (db *DB) BTTL(key []byte) (int64, error) { + +// } + +// func (db *DB) BScan(key []byte, count int, inclusive bool) ([]KVPair, error) { + +// } + +// func (db *DB) bFlush() (drop int64, err error) { + +// } diff --git a/ledis/t_bin_test.go b/ledis/t_bin_test.go new file mode 100644 index 0000000..4a634cb --- /dev/null +++ b/ledis/t_bin_test.go @@ -0,0 +1,58 @@ +package ledis + +import ( + "testing" +) + +func cmpBytes(a []byte, b []byte) bool { + if len(a) != len(b) { + return true + } + + for i, n := range a { + if n != b[i] { + return true + } + } + return false +} + +func TestBinary(t *testing.T) { + db := getTestDB() + + key := []byte("test_bin") + + if v, _ := db.BGetBit(key, 100); v != 0 { + t.Error(v) + } + + if ori, _ := db.BSetBit(key, 50, 1); ori != 0 { + t.Error(ori) + } + + if v, _ := db.BGetBit(key, 50); v != 1 { + t.Error(v) + } + + if ori, _ := db.BSetBit(key, 50, 0); ori != 1 { + t.Error(ori) + } + + if v, _ := db.BGetBit(key, 50); v != 0 { + t.Error(v) + } + + db.BSetBit(key, 7, 1) + db.BSetBit(key, 8, 1) + db.BSetBit(key, 9, 1) + db.BSetBit(key, 10, 1) + + if sum, _ := db.BCount(key, 0, -1); sum != 4 { + t.Error(sum) + } + + data, _ := db.BGet(key) + if cmpBytes(data, []byte{0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00}) { + t.Error(data) + } +}