github.com/hdt3213/godis@v1.2.9/database/keys.go (about)

     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  	"strconv"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  // execDel removes a key from db
    19  func execDel(db *DB, args [][]byte) redis.Reply {
    20  	keys := make([]string, len(args))
    21  	for i, v := range args {
    22  		keys[i] = string(v)
    23  	}
    24  
    25  	deleted := db.Removes(keys...)
    26  	if deleted > 0 {
    27  		db.addAof(utils.ToCmdLine3("del", args...))
    28  	}
    29  	return protocol.MakeIntReply(int64(deleted))
    30  }
    31  
    32  func undoDel(db *DB, args [][]byte) []CmdLine {
    33  	keys := make([]string, len(args))
    34  	for i, v := range args {
    35  		keys[i] = string(v)
    36  	}
    37  	return rollbackGivenKeys(db, keys...)
    38  }
    39  
    40  // execExists checks if given key is existed in db
    41  func execExists(db *DB, args [][]byte) redis.Reply {
    42  	result := int64(0)
    43  	for _, arg := range args {
    44  		key := string(arg)
    45  		_, exists := db.GetEntity(key)
    46  		if exists {
    47  			result++
    48  		}
    49  	}
    50  	return protocol.MakeIntReply(result)
    51  }
    52  
    53  // execFlushDB removes all data in current db
    54  // deprecated, use Server.flushDB
    55  func execFlushDB(db *DB, args [][]byte) redis.Reply {
    56  	db.Flush()
    57  	db.addAof(utils.ToCmdLine3("flushdb", args...))
    58  	return &protocol.OkReply{}
    59  }
    60  
    61  // execType returns the type of entity, including: string, list, hash, set and zset
    62  func execType(db *DB, args [][]byte) redis.Reply {
    63  	key := string(args[0])
    64  	entity, exists := db.GetEntity(key)
    65  	if !exists {
    66  		return protocol.MakeStatusReply("none")
    67  	}
    68  	switch entity.Data.(type) {
    69  	case []byte:
    70  		return protocol.MakeStatusReply("string")
    71  	case list.List:
    72  		return protocol.MakeStatusReply("list")
    73  	case dict.Dict:
    74  		return protocol.MakeStatusReply("hash")
    75  	case *set.Set:
    76  		return protocol.MakeStatusReply("set")
    77  	case *sortedset.SortedSet:
    78  		return protocol.MakeStatusReply("zset")
    79  	}
    80  	return &protocol.UnknownErrReply{}
    81  }
    82  
    83  func prepareRename(args [][]byte) ([]string, []string) {
    84  	src := string(args[0])
    85  	dest := string(args[1])
    86  	return []string{dest}, []string{src}
    87  }
    88  
    89  // execRename a key
    90  func execRename(db *DB, args [][]byte) redis.Reply {
    91  	if len(args) != 2 {
    92  		return protocol.MakeErrReply("ERR wrong number of arguments for 'rename' command")
    93  	}
    94  	src := string(args[0])
    95  	dest := string(args[1])
    96  
    97  	entity, ok := db.GetEntity(src)
    98  	if !ok {
    99  		return protocol.MakeErrReply("no such key")
   100  	}
   101  	rawTTL, hasTTL := db.ttlMap.Get(src)
   102  	db.PutEntity(dest, entity)
   103  	db.Remove(src)
   104  	if hasTTL {
   105  		db.Persist(src) // clean src and dest with their ttl
   106  		db.Persist(dest)
   107  		expireTime, _ := rawTTL.(time.Time)
   108  		db.Expire(dest, expireTime)
   109  	}
   110  	db.addAof(utils.ToCmdLine3("rename", args...))
   111  	return &protocol.OkReply{}
   112  }
   113  
   114  func undoRename(db *DB, args [][]byte) []CmdLine {
   115  	src := string(args[0])
   116  	dest := string(args[1])
   117  	return rollbackGivenKeys(db, src, dest)
   118  }
   119  
   120  // execRenameNx a key, only if the new key does not exist
   121  func execRenameNx(db *DB, args [][]byte) redis.Reply {
   122  	src := string(args[0])
   123  	dest := string(args[1])
   124  
   125  	_, ok := db.GetEntity(dest)
   126  	if ok {
   127  		return protocol.MakeIntReply(0)
   128  	}
   129  
   130  	entity, ok := db.GetEntity(src)
   131  	if !ok {
   132  		return protocol.MakeErrReply("no such key")
   133  	}
   134  	rawTTL, hasTTL := db.ttlMap.Get(src)
   135  	db.Removes(src, dest) // clean src and dest with their ttl
   136  	db.PutEntity(dest, entity)
   137  	if hasTTL {
   138  		db.Persist(src) // clean src and dest with their ttl
   139  		db.Persist(dest)
   140  		expireTime, _ := rawTTL.(time.Time)
   141  		db.Expire(dest, expireTime)
   142  	}
   143  	db.addAof(utils.ToCmdLine3("renamenx", args...))
   144  	return protocol.MakeIntReply(1)
   145  }
   146  
   147  // execExpire sets a key's time to live in seconds
   148  func execExpire(db *DB, args [][]byte) redis.Reply {
   149  	key := string(args[0])
   150  
   151  	ttlArg, err := strconv.ParseInt(string(args[1]), 10, 64)
   152  	if err != nil {
   153  		return protocol.MakeErrReply("ERR value is not an integer or out of range")
   154  	}
   155  	ttl := time.Duration(ttlArg) * time.Second
   156  
   157  	_, exists := db.GetEntity(key)
   158  	if !exists {
   159  		return protocol.MakeIntReply(0)
   160  	}
   161  
   162  	expireAt := time.Now().Add(ttl)
   163  	db.Expire(key, expireAt)
   164  	db.addAof(aof.MakeExpireCmd(key, expireAt).Args)
   165  	return protocol.MakeIntReply(1)
   166  }
   167  
   168  // execExpireAt sets a key's expiration in unix timestamp
   169  func execExpireAt(db *DB, args [][]byte) redis.Reply {
   170  	key := string(args[0])
   171  
   172  	raw, err := strconv.ParseInt(string(args[1]), 10, 64)
   173  	if err != nil {
   174  		return protocol.MakeErrReply("ERR value is not an integer or out of range")
   175  	}
   176  	expireAt := time.Unix(raw, 0)
   177  
   178  	_, exists := db.GetEntity(key)
   179  	if !exists {
   180  		return protocol.MakeIntReply(0)
   181  	}
   182  
   183  	db.Expire(key, expireAt)
   184  	db.addAof(aof.MakeExpireCmd(key, expireAt).Args)
   185  	return protocol.MakeIntReply(1)
   186  }
   187  
   188  // execExpireTime returns the absolute Unix expiration timestamp in seconds at which the given key will expire.
   189  func execExpireTime(db *DB, args [][]byte) redis.Reply {
   190  	key := string(args[0])
   191  	_, exists := db.GetEntity(key)
   192  	if !exists {
   193  		return protocol.MakeIntReply(-2)
   194  	}
   195  
   196  	raw, exists := db.ttlMap.Get(key)
   197  	if !exists {
   198  		return protocol.MakeIntReply(-1)
   199  	}
   200  	rawExpireTime, _ := raw.(time.Time)
   201  	expireTime := rawExpireTime.Unix()
   202  	return protocol.MakeIntReply(expireTime)
   203  }
   204  
   205  // execPExpire sets a key's time to live in milliseconds
   206  func execPExpire(db *DB, args [][]byte) redis.Reply {
   207  	key := string(args[0])
   208  
   209  	ttlArg, err := strconv.ParseInt(string(args[1]), 10, 64)
   210  	if err != nil {
   211  		return protocol.MakeErrReply("ERR value is not an integer or out of range")
   212  	}
   213  	ttl := time.Duration(ttlArg) * time.Millisecond
   214  
   215  	_, exists := db.GetEntity(key)
   216  	if !exists {
   217  		return protocol.MakeIntReply(0)
   218  	}
   219  
   220  	expireAt := time.Now().Add(ttl)
   221  	db.Expire(key, expireAt)
   222  	db.addAof(aof.MakeExpireCmd(key, expireAt).Args)
   223  	return protocol.MakeIntReply(1)
   224  }
   225  
   226  // execPExpireAt sets a key's expiration in unix timestamp specified in milliseconds
   227  func execPExpireAt(db *DB, args [][]byte) redis.Reply {
   228  	key := string(args[0])
   229  
   230  	raw, err := strconv.ParseInt(string(args[1]), 10, 64)
   231  	if err != nil {
   232  		return protocol.MakeErrReply("ERR value is not an integer or out of range")
   233  	}
   234  	expireAt := time.Unix(0, raw*int64(time.Millisecond))
   235  
   236  	_, exists := db.GetEntity(key)
   237  	if !exists {
   238  		return protocol.MakeIntReply(0)
   239  	}
   240  
   241  	db.Expire(key, expireAt)
   242  
   243  	db.addAof(aof.MakeExpireCmd(key, expireAt).Args)
   244  	return protocol.MakeIntReply(1)
   245  }
   246  
   247  // execPExpireTime returns the absolute Unix expiration timestamp in milliseconds at which the given key will expire.
   248  func execPExpireTime(db *DB, args [][]byte) redis.Reply {
   249  	key := string(args[0])
   250  	_, exists := db.GetEntity(key)
   251  	if !exists {
   252  		return protocol.MakeIntReply(-2)
   253  	}
   254  
   255  	raw, exists := db.ttlMap.Get(key)
   256  	if !exists {
   257  		return protocol.MakeIntReply(-1)
   258  	}
   259  	rawExpireTime, _ := raw.(time.Time)
   260  	expireTime := rawExpireTime.UnixMilli()
   261  	return protocol.MakeIntReply(expireTime)
   262  }
   263  
   264  // execTTL returns a key's time to live in seconds
   265  func execTTL(db *DB, args [][]byte) redis.Reply {
   266  	key := string(args[0])
   267  	_, exists := db.GetEntity(key)
   268  	if !exists {
   269  		return protocol.MakeIntReply(-2)
   270  	}
   271  
   272  	raw, exists := db.ttlMap.Get(key)
   273  	if !exists {
   274  		return protocol.MakeIntReply(-1)
   275  	}
   276  	expireTime, _ := raw.(time.Time)
   277  	ttl := expireTime.Sub(time.Now())
   278  	return protocol.MakeIntReply(int64(ttl / time.Second))
   279  }
   280  
   281  // execPTTL returns a key's time to live in milliseconds
   282  func execPTTL(db *DB, args [][]byte) redis.Reply {
   283  	key := string(args[0])
   284  	_, exists := db.GetEntity(key)
   285  	if !exists {
   286  		return protocol.MakeIntReply(-2)
   287  	}
   288  
   289  	raw, exists := db.ttlMap.Get(key)
   290  	if !exists {
   291  		return protocol.MakeIntReply(-1)
   292  	}
   293  	expireTime, _ := raw.(time.Time)
   294  	ttl := expireTime.Sub(time.Now())
   295  	return protocol.MakeIntReply(int64(ttl / time.Millisecond))
   296  }
   297  
   298  // execPersist removes expiration from a key
   299  func execPersist(db *DB, args [][]byte) redis.Reply {
   300  	key := string(args[0])
   301  	_, exists := db.GetEntity(key)
   302  	if !exists {
   303  		return protocol.MakeIntReply(0)
   304  	}
   305  
   306  	_, exists = db.ttlMap.Get(key)
   307  	if !exists {
   308  		return protocol.MakeIntReply(0)
   309  	}
   310  
   311  	db.Persist(key)
   312  	db.addAof(utils.ToCmdLine3("persist", args...))
   313  	return protocol.MakeIntReply(1)
   314  }
   315  
   316  // execKeys returns all keys matching the given pattern
   317  func execKeys(db *DB, args [][]byte) redis.Reply {
   318  	pattern, err := wildcard.CompilePattern(string(args[0]))
   319  	if err != nil {
   320  		return protocol.MakeErrReply("ERR illegal wildcard")
   321  	}
   322  	result := make([][]byte, 0)
   323  	db.data.ForEach(func(key string, val interface{}) bool {
   324  		if pattern.IsMatch(key) {
   325  			result = append(result, []byte(key))
   326  		}
   327  		return true
   328  	})
   329  	return protocol.MakeMultiBulkReply(result)
   330  }
   331  
   332  func toTTLCmd(db *DB, key string) *protocol.MultiBulkReply {
   333  	raw, exists := db.ttlMap.Get(key)
   334  	if !exists {
   335  		// has no TTL
   336  		return protocol.MakeMultiBulkReply(utils.ToCmdLine("PERSIST", key))
   337  	}
   338  	expireTime, _ := raw.(time.Time)
   339  	timestamp := strconv.FormatInt(expireTime.UnixNano()/1000/1000, 10)
   340  	return protocol.MakeMultiBulkReply(utils.ToCmdLine("PEXPIREAT", key, timestamp))
   341  }
   342  
   343  func undoExpire(db *DB, args [][]byte) []CmdLine {
   344  	key := string(args[0])
   345  	return []CmdLine{
   346  		toTTLCmd(db, key).Args,
   347  	}
   348  }
   349  
   350  // execCopy usage: COPY source destination [DB destination-db] [REPLACE]
   351  // This command copies the value stored at the source key to the destination key.
   352  func execCopy(mdb *Server, conn redis.Connection, args [][]byte) redis.Reply {
   353  	dbIndex := conn.GetDBIndex()
   354  	db := mdb.mustSelectDB(dbIndex) // Current DB
   355  	replaceFlag := false
   356  	srcKey := string(args[0])
   357  	destKey := string(args[1])
   358  
   359  	// Parse options
   360  	if len(args) > 2 {
   361  		for i := 2; i < len(args); i++ {
   362  			arg := strings.ToLower(string(args[i]))
   363  			if arg == "db" {
   364  				if i+1 >= len(args) {
   365  					return &protocol.SyntaxErrReply{}
   366  				}
   367  				idx, err := strconv.Atoi(string(args[i+1]))
   368  				if err != nil {
   369  					return &protocol.SyntaxErrReply{}
   370  				}
   371  				if idx >= len(mdb.dbSet) || idx < 0 {
   372  					return protocol.MakeErrReply("ERR DB index is out of range")
   373  				}
   374  				dbIndex = idx
   375  				i++
   376  			} else if arg == "replace" {
   377  				replaceFlag = true
   378  			} else {
   379  				return &protocol.SyntaxErrReply{}
   380  			}
   381  		}
   382  	}
   383  
   384  	if srcKey == destKey && dbIndex == conn.GetDBIndex() {
   385  		return protocol.MakeErrReply("ERR source and destination objects are the same")
   386  	}
   387  
   388  	// source key does not exist
   389  	src, exists := db.GetEntity(srcKey)
   390  	if !exists {
   391  		return protocol.MakeIntReply(0)
   392  	}
   393  
   394  	destDB := mdb.mustSelectDB(dbIndex)
   395  	if _, exists = destDB.GetEntity(destKey); exists != false {
   396  		// If destKey exists and there is no "replace" option
   397  		if replaceFlag == false {
   398  			return protocol.MakeIntReply(0)
   399  		}
   400  	}
   401  
   402  	destDB.PutEntity(destKey, src)
   403  	raw, exists := db.ttlMap.Get(srcKey)
   404  	if exists {
   405  		expire := raw.(time.Time)
   406  		destDB.Expire(destKey, expire)
   407  	}
   408  	mdb.AddAof(conn.GetDBIndex(), utils.ToCmdLine3("copy", args...))
   409  	return protocol.MakeIntReply(1)
   410  }
   411  
   412  func init() {
   413  	registerCommand("Del", execDel, writeAllKeys, undoDel, -2, flagWrite).
   414  		attachCommandExtra([]string{redisFlagWrite}, 1, -1, 1)
   415  	registerCommand("Expire", execExpire, writeFirstKey, undoExpire, 3, flagWrite).
   416  		attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
   417  	registerCommand("ExpireAt", execExpireAt, writeFirstKey, undoExpire, 3, flagWrite).
   418  		attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
   419  	registerCommand("ExpireTime", execExpireTime, readFirstKey, nil, 2, flagReadOnly).
   420  		attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
   421  	registerCommand("PExpire", execPExpire, writeFirstKey, undoExpire, 3, flagWrite).
   422  		attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
   423  	registerCommand("PExpireAt", execPExpireAt, writeFirstKey, undoExpire, 3, flagWrite).
   424  		attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
   425  	registerCommand("PExpireTime", execPExpireTime, readFirstKey, nil, 2, flagReadOnly).
   426  		attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
   427  	registerCommand("TTL", execTTL, readFirstKey, nil, 2, flagReadOnly).
   428  		attachCommandExtra([]string{redisFlagReadonly, redisFlagRandom, redisFlagFast}, 1, 1, 1)
   429  	registerCommand("PTTL", execPTTL, readFirstKey, nil, 2, flagReadOnly).
   430  		attachCommandExtra([]string{redisFlagReadonly, redisFlagRandom, redisFlagFast}, 1, 1, 1)
   431  	registerCommand("Persist", execPersist, writeFirstKey, undoExpire, 2, flagWrite).
   432  		attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
   433  	registerCommand("Exists", execExists, readAllKeys, nil, -2, flagReadOnly).
   434  		attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
   435  	registerCommand("Type", execType, readFirstKey, nil, 2, flagReadOnly).
   436  		attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
   437  	registerCommand("Rename", execRename, prepareRename, undoRename, 3, flagReadOnly).
   438  		attachCommandExtra([]string{redisFlagWrite}, 1, 1, 1)
   439  	registerCommand("RenameNx", execRenameNx, prepareRename, undoRename, 3, flagReadOnly).
   440  		attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
   441  	registerCommand("Keys", execKeys, noPrepare, nil, 2, flagReadOnly).
   442  		attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1)
   443  }