• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

HDT3213 / godis / 13112161214

03 Feb 2025 11:10AM UTC coverage: 73.919% (+0.9%) from 73.041%
13112161214

push

github

HDT3213
fix test suits

6550 of 8861 relevant lines covered (73.92%)

0.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.95
/database/keys.go
1
package database
2

3
import (
4
        "github.com/hdt3213/godis/aof"
5
        "github.com/hdt3213/godis/datastruct/dict"
6
        "github.com/hdt3213/godis/datastruct/list"
7
        "github.com/hdt3213/godis/datastruct/set"
8
        "github.com/hdt3213/godis/datastruct/sortedset"
9
        "github.com/hdt3213/godis/interface/redis"
10
        "github.com/hdt3213/godis/lib/utils"
11
        "github.com/hdt3213/godis/lib/wildcard"
12
        "github.com/hdt3213/godis/redis/protocol"
13
        "math"
14
        "strconv"
15
        "strings"
16
        "time"
17
)
18

19
// execDel removes a key from db
20
func execDel(db *DB, args [][]byte) redis.Reply {
1✔
21
        keys := make([]string, len(args))
1✔
22
        for i, v := range args {
2✔
23
                keys[i] = string(v)
1✔
24
        }
1✔
25

26
        deleted := db.Removes(keys...)
1✔
27
        if deleted > 0 {
2✔
28
                db.addAof(utils.ToCmdLine3("del", args...))
1✔
29
        }
1✔
30
        return protocol.MakeIntReply(int64(deleted))
1✔
31
}
32

33
func undoDel(db *DB, args [][]byte) []CmdLine {
×
34
        keys := make([]string, len(args))
×
35
        for i, v := range args {
×
36
                keys[i] = string(v)
×
37
        }
×
38
        return rollbackGivenKeys(db, keys...)
×
39
}
40

41
// execExists checks if given key is existed in db
42
func execExists(db *DB, args [][]byte) redis.Reply {
1✔
43
        result := int64(0)
1✔
44
        for _, arg := range args {
2✔
45
                key := string(arg)
1✔
46
                _, exists := db.GetEntity(key)
1✔
47
                if exists {
2✔
48
                        result++
1✔
49
                }
1✔
50
        }
51
        return protocol.MakeIntReply(result)
1✔
52
}
53

54
// execFlushDB removes all data in current db
55
// deprecated, use Server.flushDB
56
func execFlushDB(db *DB, args [][]byte) redis.Reply {
1✔
57
        db.Flush()
1✔
58
        db.addAof(utils.ToCmdLine3("flushdb", args...))
1✔
59
        return &protocol.OkReply{}
1✔
60
}
1✔
61

62
// returns the type of entity, including: string, list, hash, set and zset
63
func getType(db *DB, key string) string {
1✔
64
        entity, exists := db.GetEntity(key)
1✔
65
        if !exists {
2✔
66
                return "none"
1✔
67
        }
1✔
68
        switch entity.Data.(type) {
1✔
69
        case []byte:
1✔
70
                return "string"
1✔
71
        case list.List:
1✔
72
                return "list"
1✔
73
        case dict.Dict:
1✔
74
                return "hash"
1✔
75
        case *set.Set:
1✔
76
                return "set"
1✔
77
        case *sortedset.SortedSet:
1✔
78
                return "zset"
1✔
79
        }
80
        return ""
×
81
}
82

83
// execType returns the type of entity, including: string, list, hash, set and zset
84
func execType(db *DB, args [][]byte) redis.Reply {
1✔
85
        key := string(args[0])
1✔
86
        result := getType(db, key)
1✔
87
        if len(result) > 0 {
2✔
88
                return protocol.MakeStatusReply(result)
1✔
89
        } else {
1✔
90
                return &protocol.UnknownErrReply{}
×
91
        }
×
92
}
93

94
func prepareRename(args [][]byte) ([]string, []string) {
1✔
95
        src := string(args[0])
1✔
96
        dest := string(args[1])
1✔
97
        return []string{dest}, []string{src}
1✔
98
}
1✔
99

100
// execRename a key
101
func execRename(db *DB, args [][]byte) redis.Reply {
1✔
102
        if len(args) != 2 {
1✔
103
                return protocol.MakeErrReply("ERR wrong number of arguments for 'rename' command")
×
104
        }
×
105
        src := string(args[0])
1✔
106
        dest := string(args[1])
1✔
107

1✔
108
        entity, ok := db.GetEntity(src)
1✔
109
        if !ok {
1✔
110
                return protocol.MakeErrReply("no such key")
×
111
        }
×
112
        rawTTL, hasTTL := db.ttlMap.Get(src)
1✔
113
        db.PutEntity(dest, entity)
1✔
114
        db.Remove(src)
1✔
115
        if hasTTL {
2✔
116
                db.Persist(src) // clean src and dest with their ttl
1✔
117
                db.Persist(dest)
1✔
118
                expireTime, _ := rawTTL.(time.Time)
1✔
119
                db.Expire(dest, expireTime)
1✔
120
        }
1✔
121
        db.addAof(utils.ToCmdLine3("rename", args...))
1✔
122
        return &protocol.OkReply{}
1✔
123
}
124

125
func undoRename(db *DB, args [][]byte) []CmdLine {
×
126
        src := string(args[0])
×
127
        dest := string(args[1])
×
128
        return rollbackGivenKeys(db, src, dest)
×
129
}
×
130

131
// execRenameNx a key, only if the new key does not exist
132
func execRenameNx(db *DB, args [][]byte) redis.Reply {
1✔
133
        src := string(args[0])
1✔
134
        dest := string(args[1])
1✔
135

1✔
136
        _, ok := db.GetEntity(dest)
1✔
137
        if ok {
1✔
138
                return protocol.MakeIntReply(0)
×
139
        }
×
140

141
        entity, ok := db.GetEntity(src)
1✔
142
        if !ok {
1✔
143
                return protocol.MakeErrReply("no such key")
×
144
        }
×
145
        rawTTL, hasTTL := db.ttlMap.Get(src)
1✔
146
        db.Removes(src, dest) // clean src and dest with their ttl
1✔
147
        db.PutEntity(dest, entity)
1✔
148
        if hasTTL {
2✔
149
                db.Persist(src) // clean src and dest with their ttl
1✔
150
                db.Persist(dest)
1✔
151
                expireTime, _ := rawTTL.(time.Time)
1✔
152
                db.Expire(dest, expireTime)
1✔
153
        }
1✔
154
        db.addAof(utils.ToCmdLine3("renamenx", args...))
1✔
155
        return protocol.MakeIntReply(1)
1✔
156
}
157

158
// execExpire sets a key's time to live in seconds
159
func execExpire(db *DB, args [][]byte) redis.Reply {
1✔
160
        key := string(args[0])
1✔
161

1✔
162
        ttlArg, err := strconv.ParseInt(string(args[1]), 10, 64)
1✔
163
        if err != nil {
1✔
164
                return protocol.MakeErrReply("ERR value is not an integer or out of range")
×
165
        }
×
166
        ttl := time.Duration(ttlArg) * time.Second
1✔
167

1✔
168
        _, exists := db.GetEntity(key)
1✔
169
        if !exists {
1✔
170
                return protocol.MakeIntReply(0)
×
171
        }
×
172

173
        expireAt := time.Now().Add(ttl)
1✔
174
        db.Expire(key, expireAt)
1✔
175
        db.addAof(aof.MakeExpireCmd(key, expireAt).Args)
1✔
176
        return protocol.MakeIntReply(1)
1✔
177
}
178

179
// execExpireAt sets a key's expiration in unix timestamp
180
func execExpireAt(db *DB, args [][]byte) redis.Reply {
1✔
181
        key := string(args[0])
1✔
182

1✔
183
        raw, err := strconv.ParseInt(string(args[1]), 10, 64)
1✔
184
        if err != nil {
1✔
185
                return protocol.MakeErrReply("ERR value is not an integer or out of range")
×
186
        }
×
187
        expireAt := time.Unix(raw, 0)
1✔
188

1✔
189
        _, exists := db.GetEntity(key)
1✔
190
        if !exists {
1✔
191
                return protocol.MakeIntReply(0)
×
192
        }
×
193

194
        db.Expire(key, expireAt)
1✔
195
        db.addAof(aof.MakeExpireCmd(key, expireAt).Args)
1✔
196
        return protocol.MakeIntReply(1)
1✔
197
}
198

199
// execExpireTime returns the absolute Unix expiration timestamp in seconds at which the given key will expire.
200
func execExpireTime(db *DB, args [][]byte) redis.Reply {
1✔
201
        key := string(args[0])
1✔
202
        _, exists := db.GetEntity(key)
1✔
203
        if !exists {
2✔
204
                return protocol.MakeIntReply(-2)
1✔
205
        }
1✔
206

207
        raw, exists := db.ttlMap.Get(key)
1✔
208
        if !exists {
2✔
209
                return protocol.MakeIntReply(-1)
1✔
210
        }
1✔
211
        rawExpireTime, _ := raw.(time.Time)
1✔
212
        expireTime := rawExpireTime.Unix()
1✔
213
        return protocol.MakeIntReply(expireTime)
1✔
214
}
215

216
// execPExpire sets a key's time to live in milliseconds
217
func execPExpire(db *DB, args [][]byte) redis.Reply {
1✔
218
        key := string(args[0])
1✔
219

1✔
220
        ttlArg, err := strconv.ParseInt(string(args[1]), 10, 64)
1✔
221
        if err != nil {
1✔
222
                return protocol.MakeErrReply("ERR value is not an integer or out of range")
×
223
        }
×
224
        ttl := time.Duration(ttlArg) * time.Millisecond
1✔
225

1✔
226
        _, exists := db.GetEntity(key)
1✔
227
        if !exists {
1✔
228
                return protocol.MakeIntReply(0)
×
229
        }
×
230

231
        expireAt := time.Now().Add(ttl)
1✔
232
        db.Expire(key, expireAt)
1✔
233
        db.addAof(aof.MakeExpireCmd(key, expireAt).Args)
1✔
234
        return protocol.MakeIntReply(1)
1✔
235
}
236

237
// execPExpireAt sets a key's expiration in unix timestamp specified in milliseconds
238
func execPExpireAt(db *DB, args [][]byte) redis.Reply {
1✔
239
        key := string(args[0])
1✔
240

1✔
241
        raw, err := strconv.ParseInt(string(args[1]), 10, 64)
1✔
242
        if err != nil {
1✔
243
                return protocol.MakeErrReply("ERR value is not an integer or out of range")
×
244
        }
×
245
        expireAt := time.Unix(0, raw*int64(time.Millisecond))
1✔
246

1✔
247
        _, exists := db.GetEntity(key)
1✔
248
        if !exists {
1✔
249
                return protocol.MakeIntReply(0)
×
250
        }
×
251

252
        db.Expire(key, expireAt)
1✔
253

1✔
254
        db.addAof(aof.MakeExpireCmd(key, expireAt).Args)
1✔
255
        return protocol.MakeIntReply(1)
1✔
256
}
257

258
// execPExpireTime returns the absolute Unix expiration timestamp in milliseconds at which the given key will expire.
259
func execPExpireTime(db *DB, args [][]byte) redis.Reply {
1✔
260
        key := string(args[0])
1✔
261
        _, exists := db.GetEntity(key)
1✔
262
        if !exists {
2✔
263
                return protocol.MakeIntReply(-2)
1✔
264
        }
1✔
265

266
        raw, exists := db.ttlMap.Get(key)
1✔
267
        if !exists {
2✔
268
                return protocol.MakeIntReply(-1)
1✔
269
        }
1✔
270
        rawExpireTime, _ := raw.(time.Time)
1✔
271
        expireTime := rawExpireTime.UnixMilli()
1✔
272
        return protocol.MakeIntReply(expireTime)
1✔
273
}
274

275
// execTTL returns a key's time to live in seconds
276
func execTTL(db *DB, args [][]byte) redis.Reply {
1✔
277
        key := string(args[0])
1✔
278
        _, exists := db.GetEntity(key)
1✔
279
        if !exists {
2✔
280
                return protocol.MakeIntReply(-2)
1✔
281
        }
1✔
282

283
        raw, exists := db.ttlMap.Get(key)
1✔
284
        if !exists {
2✔
285
                return protocol.MakeIntReply(-1)
1✔
286
        }
1✔
287
        expireTime, _ := raw.(time.Time)
1✔
288
        ttl := expireTime.Sub(time.Now()).Seconds()
1✔
289
        return protocol.MakeIntReply(int64(math.Round(ttl)))
1✔
290
}
291

292
// execPTTL returns a key's time to live in milliseconds
293
func execPTTL(db *DB, args [][]byte) redis.Reply {
1✔
294
        key := string(args[0])
1✔
295
        _, exists := db.GetEntity(key)
1✔
296
        if !exists {
1✔
297
                return protocol.MakeIntReply(-2)
×
298
        }
×
299

300
        raw, exists := db.ttlMap.Get(key)
1✔
301
        if !exists {
1✔
302
                return protocol.MakeIntReply(-1)
×
303
        }
×
304
        expireTime, _ := raw.(time.Time)
1✔
305
        ttl := expireTime.Sub(time.Now()).Milliseconds()
1✔
306
        return protocol.MakeIntReply(int64(math.Round(float64(ttl))))
1✔
307
}
308

309
// execPersist removes expiration from a key
310
func execPersist(db *DB, args [][]byte) redis.Reply {
1✔
311
        key := string(args[0])
1✔
312
        _, exists := db.GetEntity(key)
1✔
313
        if !exists {
1✔
314
                return protocol.MakeIntReply(0)
×
315
        }
×
316

317
        _, exists = db.ttlMap.Get(key)
1✔
318
        if !exists {
2✔
319
                return protocol.MakeIntReply(0)
1✔
320
        }
1✔
321

322
        db.Persist(key)
1✔
323
        db.addAof(utils.ToCmdLine3("persist", args...))
1✔
324
        return protocol.MakeIntReply(1)
1✔
325
}
326

327
// execKeys returns all keys matching the given pattern
328
func execKeys(db *DB, args [][]byte) redis.Reply {
1✔
329
        pattern, err := wildcard.CompilePattern(string(args[0]))
1✔
330
        if err != nil {
1✔
331
                return protocol.MakeErrReply("ERR illegal wildcard")
×
332
        }
×
333
        result := make([][]byte, 0)
1✔
334
        db.data.ForEach(func(key string, val interface{}) bool {
2✔
335
                if !pattern.IsMatch(key) {
2✔
336
                        return true
1✔
337
                }
1✔
338
                if !db.IsExpired(key) {
2✔
339
                        result = append(result, []byte(key))
1✔
340
                }
1✔
341
                return true
1✔
342
        })
343
        return protocol.MakeMultiBulkReply(result)
1✔
344
}
345

346
func toTTLCmd(db *DB, key string) *protocol.MultiBulkReply {
1✔
347
        raw, exists := db.ttlMap.Get(key)
1✔
348
        if !exists {
2✔
349
                // has no TTL
1✔
350
                return protocol.MakeMultiBulkReply(utils.ToCmdLine("PERSIST", key))
1✔
351
        }
1✔
352
        expireTime, _ := raw.(time.Time)
1✔
353
        timestamp := strconv.FormatInt(expireTime.UnixNano()/1000/1000, 10)
1✔
354
        return protocol.MakeMultiBulkReply(utils.ToCmdLine("PEXPIREAT", key, timestamp))
1✔
355
}
356

357
func undoExpire(db *DB, args [][]byte) []CmdLine {
×
358
        key := string(args[0])
×
359
        return []CmdLine{
×
360
                toTTLCmd(db, key).Args,
×
361
        }
×
362
}
×
363

364
// execCopy usage: COPY source destination [DB destination-db] [REPLACE]
365
// This command copies the value stored at the source key to the destination key.
366
func execCopy(mdb *Server, conn redis.Connection, args [][]byte) redis.Reply {
1✔
367
        dbIndex := conn.GetDBIndex()
1✔
368
        db := mdb.mustSelectDB(dbIndex) // Current DB
1✔
369
        replaceFlag := false
1✔
370
        srcKey := string(args[0])
1✔
371
        destKey := string(args[1])
1✔
372

1✔
373
        // Parse options
1✔
374
        if len(args) > 2 {
2✔
375
                for i := 2; i < len(args); i++ {
2✔
376
                        arg := strings.ToLower(string(args[i]))
1✔
377
                        if arg == "db" {
2✔
378
                                if i+1 >= len(args) {
1✔
379
                                        return &protocol.SyntaxErrReply{}
×
380
                                }
×
381
                                idx, err := strconv.Atoi(string(args[i+1]))
1✔
382
                                if err != nil {
1✔
383
                                        return &protocol.SyntaxErrReply{}
×
384
                                }
×
385
                                if idx >= len(mdb.dbSet) || idx < 0 {
1✔
386
                                        return protocol.MakeErrReply("ERR DB index is out of range")
×
387
                                }
×
388
                                dbIndex = idx
1✔
389
                                i++
1✔
390
                        } else if arg == "replace" {
2✔
391
                                replaceFlag = true
1✔
392
                        } else {
1✔
393
                                return &protocol.SyntaxErrReply{}
×
394
                        }
×
395
                }
396
        }
397

398
        if srcKey == destKey && dbIndex == conn.GetDBIndex() {
1✔
399
                return protocol.MakeErrReply("ERR source and destination objects are the same")
×
400
        }
×
401

402
        // source key does not exist
403
        src, exists := db.GetEntity(srcKey)
1✔
404
        if !exists {
1✔
405
                return protocol.MakeIntReply(0)
×
406
        }
×
407

408
        destDB := mdb.mustSelectDB(dbIndex)
1✔
409
        if _, exists = destDB.GetEntity(destKey); exists != false {
2✔
410
                // If destKey exists and there is no "replace" option
1✔
411
                if replaceFlag == false {
2✔
412
                        return protocol.MakeIntReply(0)
1✔
413
                }
1✔
414
        }
415

416
        destDB.PutEntity(destKey, src)
1✔
417
        raw, exists := db.ttlMap.Get(srcKey)
1✔
418
        if exists {
2✔
419
                expire := raw.(time.Time)
1✔
420
                destDB.Expire(destKey, expire)
1✔
421
        }
1✔
422
        mdb.AddAof(conn.GetDBIndex(), utils.ToCmdLine3("copy", args...))
1✔
423
        return protocol.MakeIntReply(1)
1✔
424
}
425

426
// execScan return the result of the scan
427
func execScan(db *DB, args [][]byte) redis.Reply {
1✔
428
        var count int = 10
1✔
429
        var pattern string = "*"
1✔
430
        var scanType string = ""
1✔
431
        if len(args) > 1 {
2✔
432
                for i := 1; i < len(args); i++ {
2✔
433
                        arg := strings.ToLower(string(args[i]))
1✔
434
                        if arg == "count" {
2✔
435
                                count0, err := strconv.Atoi(string(args[i+1]))
1✔
436
                                if err != nil {
1✔
437
                                        return &protocol.SyntaxErrReply{}
×
438
                                }
×
439
                                count = count0
1✔
440
                                i++
1✔
441
                        } else if arg == "match" {
2✔
442
                                pattern = string(args[i+1])
1✔
443
                                i++
1✔
444
                        } else if arg == "type" {
3✔
445
                                scanType = strings.ToLower(string(args[i+1]))
1✔
446
                                i++
1✔
447
                        } else {
1✔
448
                                return &protocol.SyntaxErrReply{}
×
449
                        }
×
450
                }
451
        }
452
        cursor, err := strconv.Atoi(string(args[0]))
1✔
453
        if err != nil {
1✔
454
                return protocol.MakeErrReply("ERR invalid cursor")
×
455
        }
×
456
        keysReply, nextCursor := db.data.DictScan(cursor, count, pattern)
1✔
457
        if nextCursor < 0 {
1✔
458
                return protocol.MakeErrReply("Invalid argument")
×
459
        }
×
460

461
        if len(scanType) != 0 {
2✔
462
                for i := 0; i < len(keysReply); {
2✔
463
                        if getType(db, string(keysReply[i])) != scanType {
2✔
464
                                keysReply = append(keysReply[:i], keysReply[i+1:]...)
1✔
465
                        } else {
2✔
466
                                i++
1✔
467
                        }
1✔
468
                }
469
        }
470
        result := make([]redis.Reply, 2)
1✔
471
        result[0] = protocol.MakeBulkReply([]byte(strconv.FormatInt(int64(nextCursor), 10)))
1✔
472
        result[1] = protocol.MakeMultiBulkReply(keysReply)
1✔
473

1✔
474
        return protocol.MakeMultiRawReply(result)
1✔
475
}
476

477
func init() {
1✔
478
        registerCommand("Del", execDel, writeAllKeys, undoDel, -2, flagWrite).
1✔
479
                attachCommandExtra([]string{redisFlagWrite}, 1, -1, 1)
1✔
480
        registerCommand("Expire", execExpire, writeFirstKey, undoExpire, 3, flagWrite).
1✔
481
                attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
1✔
482
        registerCommand("ExpireAt", execExpireAt, writeFirstKey, undoExpire, 3, flagWrite).
1✔
483
                attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
1✔
484
        registerCommand("ExpireTime", execExpireTime, readFirstKey, nil, 2, flagReadOnly).
1✔
485
                attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
1✔
486
        registerCommand("PExpire", execPExpire, writeFirstKey, undoExpire, 3, flagWrite).
1✔
487
                attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
1✔
488
        registerCommand("PExpireAt", execPExpireAt, writeFirstKey, undoExpire, 3, flagWrite).
1✔
489
                attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
1✔
490
        registerCommand("PExpireTime", execPExpireTime, readFirstKey, nil, 2, flagReadOnly).
1✔
491
                attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
1✔
492
        registerCommand("TTL", execTTL, readFirstKey, nil, 2, flagReadOnly).
1✔
493
                attachCommandExtra([]string{redisFlagReadonly, redisFlagRandom, redisFlagFast}, 1, 1, 1)
1✔
494
        registerCommand("PTTL", execPTTL, readFirstKey, nil, 2, flagReadOnly).
1✔
495
                attachCommandExtra([]string{redisFlagReadonly, redisFlagRandom, redisFlagFast}, 1, 1, 1)
1✔
496
        registerCommand("Persist", execPersist, writeFirstKey, undoExpire, 2, flagWrite).
1✔
497
                attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
1✔
498
        registerCommand("Exists", execExists, readAllKeys, nil, -2, flagReadOnly).
1✔
499
                attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
1✔
500
        registerCommand("Type", execType, readFirstKey, nil, 2, flagReadOnly).
1✔
501
                attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
1✔
502
        registerCommand("Rename", execRename, prepareRename, undoRename, 3, flagReadOnly).
1✔
503
                attachCommandExtra([]string{redisFlagWrite}, 1, 1, 1)
1✔
504
        registerCommand("RenameNx", execRenameNx, prepareRename, undoRename, 3, flagReadOnly).
1✔
505
                attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
1✔
506
        registerCommand("Keys", execKeys, noPrepare, nil, 2, flagReadOnly).
1✔
507
                attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1)
1✔
508
        registerCommand("Scan", execScan, noPrepare, nil, -2, flagReadOnly).
1✔
509
                attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1)
1✔
510
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc