-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbitset_redis.go
244 lines (221 loc) · 6.91 KB
/
bitset_redis.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/*
Implements bitsets - both in-memory and redis.
For in-memory, https://github.com/bits-and-blooms/bitset is used while
for redis, bitset operations of redis are used.
*/
package gostatix
import (
"bytes"
"context"
"fmt"
"io"
"encoding/base64"
"encoding/binary"
"encoding/json"
"github.com/kwertop/gostatix/internal/util"
"github.com/redis/go-redis/v9"
)
// BitSetRedis is an implementation of IBitSet.
// size is the number of bits in the bitset
// key is the redis key to the bitset data structure in redis
// Bitsets or Bitmaps are implemented in Redis using string.
// All bit operations are done on the string stored at _key_.
// For more details, please refer https://redis.io/docs/data-types/bitmaps/
type BitSetRedis struct {
size uint
key string
}
// NewBitSetRedis creates a new BitSetRedis of size _size_
func newBitSetRedis(size uint) *BitSetRedis {
bytes := make([]byte, size)
for i := range bytes {
bytes[i] = 0x00
}
key := util.GenerateRandomString(16)
_ = getRedisClient().Set(context.Background(), key, string(bytes), 0).Err()
return &BitSetRedis{size, key}
}
// FromDataRedis creates an instance of BitSetRedis after
// inserting the data passed in a redis bitset
func fromDataRedis(data []uint64) (*BitSetRedis, error) {
bitSetRedis := newBitSetRedis(uint(len(data) * wordSize))
bytes, err := uint64ArrayToByteArray(data)
if err != nil {
return nil, err
}
err = getRedisClient().Set(context.Background(), bitSetRedis.key, string(bytes), 0).Err()
if err != nil {
return nil, err
}
return bitSetRedis, nil
}
// FromRedisKey creates an instance of BitSetRedis from the
// bitset data structure saved at redis key _key_
func fromRedisKey(key string) (*BitSetRedis, error) {
setVal, err := getRedisClient().Get(context.Background(), key).Result()
if err != nil {
return nil, err
}
setValBytes := []byte(setVal)
bitSetRedis := newBitSetRedis(uint(len(setValBytes) * wordBytes))
bitSetRedis.key = key
return bitSetRedis, nil
}
// Size returns the size of the bitset saved in redis
func (bitSet BitSetRedis) getSize() uint {
return bitSet.size
}
// Key gives the key at which the bitset is saved in redis
func (bitSet BitSetRedis) getKey() string {
return bitSet.key
}
// Has checks if the bit at index _index_ is set
func (bitSet BitSetRedis) has(index uint) (bool, error) {
val, err := getRedisClient().GetBit(context.Background(), bitSet.key, int64(index)).Result()
if err != nil {
return false, err
}
return val != 0, nil
}
// HasMulti checks if the bit at the indices
// specified by _indexes_ array is set
func (bitSet BitSetRedis) hasMulti(indexes []uint) ([]bool, error) {
if len(indexes) == 0 {
return nil, fmt.Errorf("gostatix: at least 1 index is required")
}
pipe := getRedisClient().Pipeline()
ctx := context.Background()
values := make([]*redis.IntCmd, len(indexes))
for i := range indexes {
values[i] = pipe.GetBit(ctx, bitSet.key, int64(indexes[i]))
}
_, err := pipe.Exec(ctx)
if err != nil {
return nil, err
}
result := make([]bool, len(values))
for i := range values {
result[i] = values[i].Val() != 0
}
return result, nil
}
// Insert sets the bit at index specified by _index_
func (bitSet BitSetRedis) insert(index uint) (bool, error) {
err := getRedisClient().SetBit(context.Background(), bitSet.key, int64(index), 1).Err()
if err != nil {
return false, err
}
return true, nil
}
// Insert sets the bits at indices specified by array _indexes_
func (bitSet BitSetRedis) insertMulti(indexes []uint) (bool, error) {
if len(indexes) == 0 {
return false, fmt.Errorf("gostatix: at least 1 index is required")
}
pipe := getRedisClient().Pipeline()
ctx := context.Background()
for i := range indexes {
pipe.SetBit(ctx, bitSet.key, int64(indexes[i]), 1)
}
_, err := pipe.Exec(ctx)
if err != nil {
return false, err
}
return true, nil
}
// Equals checks if two BitSetRedis are equal or not
func (aSet BitSetRedis) equals(otherBitSet IBitSet) (bool, error) {
bSet, ok := otherBitSet.(*BitSetRedis)
if !ok {
return false, fmt.Errorf("invalid bitset type, should be BitSetRedis")
}
aSetVal, err1 := getRedisClient().Get(context.Background(), aSet.key).Result()
if err1 != nil {
return false, err1
}
bSetVal, err2 := getRedisClient().Get(context.Background(), bSet.key).Result()
if err2 != nil {
return false, err2
}
return aSetVal == bSetVal, nil
}
// Max returns the first set bit in the bitset starting from index 0
func (bitSet BitSetRedis) max() (uint, bool) {
index, err := getRedisClient().BitPos(context.Background(), bitSet.key, 1).Result()
if err != nil || index == -1 {
return 0, false
}
return uint(index), true
}
// BitCount returns the total number of set bits in the bitset saved in redis
func (bitSet BitSetRedis) bitCount() (uint, error) {
bitRange := &redis.BitCount{Start: 0, End: -1}
val, err := getRedisClient().BitCount(context.Background(), bitSet.key, bitRange).Result()
if err != nil {
return 0, err
}
return uint(val), nil
}
// Export returns the json marshalling of the bitset saved in redis
func (bitSet BitSetRedis) marshal() (uint, []byte, error) {
val, err := getRedisClient().Get(context.Background(), bitSet.key).Result()
if err != nil {
return 0, nil, err
}
bytes := []byte(val)
for i := range bytes {
bytes[i] = util.ConvertByteToLittleEndianByte(bytes[i])
}
util.ReverseBytes(bytes)
buf := make([]byte, wordBytes)
binary.BigEndian.PutUint64(buf, uint64(bitSet.size))
bytes = append(buf, bytes...)
data, err := json.Marshal(base64.URLEncoding.EncodeToString([]byte(bytes)))
if err != nil {
return 0, nil, err
}
return bitSet.size, data, nil
}
// Import imports the marshalled json in the byte array data into the redis bitset
func (bitSet *BitSetRedis) unmarshal(data []byte) (bool, error) {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return false, err
}
bytes, _ := base64.URLEncoding.DecodeString(s)
lenBytes := bytes[:8]
bytes = bytes[8:]
size := binary.BigEndian.Uint64(lenBytes)
bitSet.size = uint(size)
util.ReverseBytes(bytes)
for i := range bytes {
bytes[i] = util.ConvertByteToLittleEndianByte(bytes[i])
}
err = getRedisClient().Set(context.Background(), bitSet.key, string(bytes), 0).Err()
if err != nil {
return false, err
}
return true, nil
}
func uint64ArrayToByteArray(data []uint64) ([]byte, error) {
// Create a buffer to store the bytes
buf := new(bytes.Buffer)
// Write each uint64 element to the buffer
for _, value := range data {
valueBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(valueBytes, value)
for _, val := range valueBytes {
if err := binary.Write(buf, binary.LittleEndian, util.ConvertByteToLittleEndianByte(val)); err != nil {
return nil, err
}
}
}
return buf.Bytes(), nil
}
func (bitSet *BitSetRedis) writeTo(stream io.Writer) (int64, error) {
return 0, nil //bitsetredis doesn't implement WriteTo function
}
func (bitSet *BitSetRedis) readFrom(stream io.Reader) (int64, error) {
return 0, nil //bitsetredis doesn't implement ReadFrom function
}