mirror of https://github.com/ledisdb/ledisdb.git
add dump and restore support
This commit is contained in:
parent
f094ed65e3
commit
4bdbdb7697
|
@ -11,8 +11,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/boltdb/bolt",
|
"ImportPath": "github.com/boltdb/bolt",
|
||||||
"Comment": "data/v1-254-gd285804",
|
"Comment": "data/v1-256-ge65c902",
|
||||||
"Rev": "d285804df1760edf4c602ecd901be5d5e67bf982"
|
"Rev": "e65c9027c35b7ef1014db9e02686889e51aadb2e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/cupcake/rdb",
|
||||||
|
"Rev": "3454dcabd33cb8ea8261ffd6a45f4d836eb504cc"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/edsrzf/mmap-go",
|
"ImportPath": "github.com/edsrzf/mmap-go",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//This file was generated by .tools/generate_commands.py on Sun Oct 26 2014 15:14:39 +0800
|
//This file was generated by .tools/generate_commands.py on Thu Nov 27 2014 13:42:44 +0800
|
||||||
package main
|
package main
|
||||||
|
|
||||||
var helpCommands = [][]string{
|
var helpCommands = [][]string{
|
||||||
|
@ -23,6 +23,7 @@ var helpCommands = [][]string{
|
||||||
{"DECR", "key", "KV"},
|
{"DECR", "key", "KV"},
|
||||||
{"DECRBY", "key decrement", "KV"},
|
{"DECRBY", "key decrement", "KV"},
|
||||||
{"DEL", "key [key ...]", "KV"},
|
{"DEL", "key [key ...]", "KV"},
|
||||||
|
{"DUMP", "key", "KV"},
|
||||||
{"ECHO", "message", "Server"},
|
{"ECHO", "message", "Server"},
|
||||||
{"EVAL", "script numkeys key [key ...] arg [arg ...]", "Script"},
|
{"EVAL", "script numkeys key [key ...] arg [arg ...]", "Script"},
|
||||||
{"EVALSHA", "sha1 numkeys key [key ...] arg [arg ...]", "Script"},
|
{"EVALSHA", "sha1 numkeys key [key ...] arg [arg ...]", "Script"},
|
||||||
|
@ -36,6 +37,7 @@ var helpCommands = [][]string{
|
||||||
{"GETSET", " key value", "KV"},
|
{"GETSET", " key value", "KV"},
|
||||||
{"HCLEAR", "key", "Hash"},
|
{"HCLEAR", "key", "Hash"},
|
||||||
{"HDEL", "key field [field ...]", "Hash"},
|
{"HDEL", "key field [field ...]", "Hash"},
|
||||||
|
{"HDUMP", "key", "Hash"},
|
||||||
{"HEXISTS", "key field", "Hash"},
|
{"HEXISTS", "key field", "Hash"},
|
||||||
{"HEXPIRE", "key seconds", "Hash"},
|
{"HEXPIRE", "key seconds", "Hash"},
|
||||||
{"HEXPIREAT", "key timestamp", "Hash"},
|
{"HEXPIREAT", "key timestamp", "Hash"},
|
||||||
|
@ -57,6 +59,7 @@ var helpCommands = [][]string{
|
||||||
{"INCRBY", "key increment", "KV"},
|
{"INCRBY", "key increment", "KV"},
|
||||||
{"INFO", "[section]", "Server"},
|
{"INFO", "[section]", "Server"},
|
||||||
{"LCLEAR", "key", "List"},
|
{"LCLEAR", "key", "List"},
|
||||||
|
{"LDUMP", "key", "List"},
|
||||||
{"LEXPIRE", "key seconds", "List"},
|
{"LEXPIRE", "key seconds", "List"},
|
||||||
{"LEXPIREAT", "key timestamp", "List"},
|
{"LEXPIREAT", "key timestamp", "List"},
|
||||||
{"LINDEX", "key index", "List"},
|
{"LINDEX", "key index", "List"},
|
||||||
|
@ -73,6 +76,7 @@ var helpCommands = [][]string{
|
||||||
{"MSET", "key value [key value ...]", "KV"},
|
{"MSET", "key value [key value ...]", "KV"},
|
||||||
{"PERSIST", "key", "KV"},
|
{"PERSIST", "key", "KV"},
|
||||||
{"PING", "-", "Server"},
|
{"PING", "-", "Server"},
|
||||||
|
{"RESTORE", "key ttl value", "Server"},
|
||||||
{"ROLLBACK", "-", "Transaction"},
|
{"ROLLBACK", "-", "Transaction"},
|
||||||
{"RPOP", "key", "List"},
|
{"RPOP", "key", "List"},
|
||||||
{"RPUSH", "key value [value ...]", "List"},
|
{"RPUSH", "key value [value ...]", "List"},
|
||||||
|
@ -84,6 +88,7 @@ var helpCommands = [][]string{
|
||||||
{"SCRIPT LOAD", "script", "Script"},
|
{"SCRIPT LOAD", "script", "Script"},
|
||||||
{"SDIFF", "key [key ...]", "Set"},
|
{"SDIFF", "key [key ...]", "Set"},
|
||||||
{"SDIFFSTORE", "destination key [key ...]", "Set"},
|
{"SDIFFSTORE", "destination key [key ...]", "Set"},
|
||||||
|
{"SDUMP", "key", "Set"},
|
||||||
{"SELECT", "index", "Server"},
|
{"SELECT", "index", "Server"},
|
||||||
{"SET", "key value", "KV"},
|
{"SET", "key value", "KV"},
|
||||||
{"SETEX", "key seconds value", "KV"},
|
{"SETEX", "key seconds value", "KV"},
|
||||||
|
@ -112,6 +117,7 @@ var helpCommands = [][]string{
|
||||||
{"ZCARD", "key", "ZSet"},
|
{"ZCARD", "key", "ZSet"},
|
||||||
{"ZCLEAR", "key", "ZSet"},
|
{"ZCLEAR", "key", "ZSet"},
|
||||||
{"ZCOUNT", "key min max", "ZSet"},
|
{"ZCOUNT", "key min max", "ZSet"},
|
||||||
|
{"ZDUMP", "key", "ZSet"},
|
||||||
{"ZEXPIRE", "key seconds", "ZSet"},
|
{"ZEXPIRE", "key seconds", "ZSet"},
|
||||||
{"ZEXPIREAT", "key timestamp", "ZSet"},
|
{"ZEXPIREAT", "key timestamp", "ZSet"},
|
||||||
{"ZINCRBY", "key increment member", "ZSet"},
|
{"ZINCRBY", "key increment member", "ZSet"},
|
||||||
|
|
|
@ -56,5 +56,14 @@ LedisDB supplies `xscan`, `xrevscan`, etc, to fetch data iteratively and reverse
|
||||||
+ Zset: `zxscan`, `zxrevscan`
|
+ Zset: `zxscan`, `zxrevscan`
|
||||||
+ Bitmap: `bxscan`, `bxrevscan`
|
+ Bitmap: `bxscan`, `bxrevscan`
|
||||||
|
|
||||||
|
## DUMP
|
||||||
|
|
||||||
|
+ KV: `dump`
|
||||||
|
+ Hash: `hdump`
|
||||||
|
+ List: `ldump`
|
||||||
|
+ Set: `sdump`
|
||||||
|
+ ZSet: `zdump`
|
||||||
|
|
||||||
|
LedisDB supports `dump` to serialize the value with key, the data format is the same as Redis, so you can use it in Redis and vice versa.
|
||||||
|
|
||||||
Of course, LedisDB has not implemented all APIs in Redis, you can see full commands in commands.json, commands.doc or [wiki](https://github.com/siddontang/ledisdb/wiki/Commands).
|
Of course, LedisDB has not implemented all APIs in Redis, you can see full commands in commands.json, commands.doc or [wiki](https://github.com/siddontang/ledisdb/wiki/Commands).
|
|
@ -691,5 +691,42 @@
|
||||||
"arguments" : "-",
|
"arguments" : "-",
|
||||||
"group": "Server",
|
"group": "Server",
|
||||||
"readonly": false
|
"readonly": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"DUMP": {
|
||||||
|
"arguments" : "key",
|
||||||
|
"group": "KV",
|
||||||
|
"readonly": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"LDUMP": {
|
||||||
|
"arguments" : "key",
|
||||||
|
"group": "List",
|
||||||
|
"readonly": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"HDUMP": {
|
||||||
|
"arguments" : "key",
|
||||||
|
"group": "Hash",
|
||||||
|
"readonly": true
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
"SDUMP": {
|
||||||
|
"arguments" : "key",
|
||||||
|
"group": "Set",
|
||||||
|
"readonly": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"ZDUMP": {
|
||||||
|
"arguments" : "key",
|
||||||
|
"group": "ZSet",
|
||||||
|
"readonly": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"RESTORE": {
|
||||||
|
"arguments" : "key ttl value",
|
||||||
|
"group" : "Server",
|
||||||
|
"readonly" : false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ Table of Contents
|
||||||
- [PERSIST key](#persist-key)
|
- [PERSIST key](#persist-key)
|
||||||
- [XSCAN key [MATCH match] [COUNT count]](#xscan-key-match-match-count-count)
|
- [XSCAN key [MATCH match] [COUNT count]](#xscan-key-match-match-count-count)
|
||||||
- [XREVSCAN key [MATCH match] [COUNT count]](#xrevscan-key-match-match-count-count)
|
- [XREVSCAN key [MATCH match] [COUNT count]](#xrevscan-key-match-match-count-count)
|
||||||
|
- [DUMP key](#dump-key)
|
||||||
- [Hash](#hash)
|
- [Hash](#hash)
|
||||||
- [HDEL key field [field ...]](#hdel-key-field-field-)
|
- [HDEL key field [field ...]](#hdel-key-field-field-)
|
||||||
- [HEXISTS key field](#hexists-key-field)
|
- [HEXISTS key field](#hexists-key-field)
|
||||||
|
@ -49,6 +50,7 @@ Table of Contents
|
||||||
- [HPERSIST key](#hpersist-key)
|
- [HPERSIST key](#hpersist-key)
|
||||||
- [HXSCAN key [MATCH match] [COUNT count]](#hxscan-key-match-match-count-count)
|
- [HXSCAN key [MATCH match] [COUNT count]](#hxscan-key-match-match-count-count)
|
||||||
- [HXREVSCAN key [MATCH match] [COUNT count]](#hxrevscan-key-match-match-count-count)
|
- [HXREVSCAN key [MATCH match] [COUNT count]](#hxrevscan-key-match-match-count-count)
|
||||||
|
- [HDUMP key](#hdump-key)
|
||||||
- [List](#list)
|
- [List](#list)
|
||||||
- [BLPOP key [key ...] timeout](#blpop-key-key--timeout)
|
- [BLPOP key [key ...] timeout](#blpop-key-key--timeout)
|
||||||
- [BRPOP key [key ...] timeout](#brpop-key-key--timeout)
|
- [BRPOP key [key ...] timeout](#brpop-key-key--timeout)
|
||||||
|
@ -67,6 +69,7 @@ Table of Contents
|
||||||
- [LPERSIST key](#lpersist-key)
|
- [LPERSIST key](#lpersist-key)
|
||||||
- [LXSCAN key [MATCH match] [COUNT count]](#lxscan-key-match-match-count-count)
|
- [LXSCAN key [MATCH match] [COUNT count]](#lxscan-key-match-match-count-count)
|
||||||
- [LXREVSCAN key [MATCH match] [COUNT count]](#lxrevscan-key-match-match-count-count)
|
- [LXREVSCAN key [MATCH match] [COUNT count]](#lxrevscan-key-match-match-count-count)
|
||||||
|
- [LDUMP key](#ldump-key)
|
||||||
- [Set](#set)
|
- [Set](#set)
|
||||||
- [SADD key member [member ...]](#sadd-key-member-member-)
|
- [SADD key member [member ...]](#sadd-key-member-member-)
|
||||||
- [SCARD key](#scard-key)
|
- [SCARD key](#scard-key)
|
||||||
|
@ -87,6 +90,7 @@ Table of Contents
|
||||||
- [SPERSIST key](#spersist-key)
|
- [SPERSIST key](#spersist-key)
|
||||||
- [SXSCAN key [MATCH match] [COUNT count]](#sxscan-key-match-match-count-count)
|
- [SXSCAN key [MATCH match] [COUNT count]](#sxscan-key-match-match-count-count)
|
||||||
- [SXREVSCAN key [MATCH match] [COUNT count]](#sxrevscan-key-match-match-count-count)
|
- [SXREVSCAN key [MATCH match] [COUNT count]](#sxrevscan-key-match-match-count-count)
|
||||||
|
- [SDUMP key](#sdump-key)
|
||||||
- [ZSet](#zset)
|
- [ZSet](#zset)
|
||||||
- [ZADD key score member [score member ...]](#zadd-key-score-member-score-member-)
|
- [ZADD key score member [score member ...]](#zadd-key-score-member-score-member-)
|
||||||
- [ZCARD key](#zcard-key)
|
- [ZCARD key](#zcard-key)
|
||||||
|
@ -117,6 +121,7 @@ Table of Contents
|
||||||
- [ZRANGEBYLEX key min max [LIMIT offset count]](#zrangebylex-key-min-max-limit-offset-count)
|
- [ZRANGEBYLEX key min max [LIMIT offset count]](#zrangebylex-key-min-max-limit-offset-count)
|
||||||
- [ZREMRANGEBYLEX key min max](#zremrangebylex-key-min-max)
|
- [ZREMRANGEBYLEX key min max](#zremrangebylex-key-min-max)
|
||||||
- [ZLEXCOUNT key min max](#zlexcount-key-min-max)
|
- [ZLEXCOUNT key min max](#zlexcount-key-min-max)
|
||||||
|
- [ZDUMP key](#zdump-key)
|
||||||
- [Bitmap](#bitmap)
|
- [Bitmap](#bitmap)
|
||||||
- [BGET key](#bget-key)
|
- [BGET key](#bget-key)
|
||||||
- [BGETBIT key offset](#bgetbit-key-offset)
|
- [BGETBIT key offset](#bgetbit-key-offset)
|
||||||
|
@ -143,6 +148,7 @@ Table of Contents
|
||||||
- [INFO [section]](#info-section)
|
- [INFO [section]](#info-section)
|
||||||
- [TIME](#time)
|
- [TIME](#time)
|
||||||
- [CONFIG REWRITE](#config-rewrite)
|
- [CONFIG REWRITE](#config-rewrite)
|
||||||
|
- [RESTORE](#restore-key-ttl-value)
|
||||||
- [Transaction](#transaction)
|
- [Transaction](#transaction)
|
||||||
- [BEGIN](#begin)
|
- [BEGIN](#begin)
|
||||||
- [ROLLBACK](#rollback)
|
- [ROLLBACK](#rollback)
|
||||||
|
@ -583,6 +589,22 @@ ledis>xrevscan "a" count 1
|
||||||
2) []
|
2) []
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### DUMP key
|
||||||
|
|
||||||
|
Serialize the value stored at key with KV type in a Redis-specific format like RDB and return it to the user. The returned value can be synthesized back into a key using the RESTORE command.
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
bulk: the serialized value
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> set mykey 10
|
||||||
|
OK
|
||||||
|
ledis>DUMP mykey
|
||||||
|
"\x00\xc0\n\x06\x00\xf8r?\xc5\xfb\xfb_("
|
||||||
|
```
|
||||||
|
|
||||||
## Hash
|
## Hash
|
||||||
|
|
||||||
|
@ -959,6 +981,10 @@ Reverse iterate Hash keys incrementally.
|
||||||
|
|
||||||
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
|
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
|
||||||
|
|
||||||
|
### HDUMP key
|
||||||
|
|
||||||
|
See [DUMP](#dump-key) for more information.
|
||||||
|
|
||||||
## List
|
## List
|
||||||
|
|
||||||
### BLPOP key [key ...] timeout
|
### BLPOP key [key ...] timeout
|
||||||
|
@ -1293,6 +1319,10 @@ Reverse iterate list keys incrementally.
|
||||||
|
|
||||||
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
|
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
|
||||||
|
|
||||||
|
### LDUMP key
|
||||||
|
|
||||||
|
See [DUMP](#dump-key) for more information.
|
||||||
|
|
||||||
|
|
||||||
## Set
|
## Set
|
||||||
|
|
||||||
|
@ -1728,6 +1758,10 @@ Reverse iterate Set keys incrementally.
|
||||||
|
|
||||||
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
|
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
|
||||||
|
|
||||||
|
### SDUMP key
|
||||||
|
|
||||||
|
See [DUMP](#dump-key) for more information.
|
||||||
|
|
||||||
## ZSet
|
## ZSet
|
||||||
|
|
||||||
### ZADD key score member [score member ...]
|
### ZADD key score member [score member ...]
|
||||||
|
@ -2425,6 +2459,9 @@ ledis> ZLEXCOUNT myzset - [c
|
||||||
(integer) 3
|
(integer) 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### ZDUMP key
|
||||||
|
|
||||||
|
See [DUMP](#dump-key) for more information.
|
||||||
|
|
||||||
## Bitmap
|
## Bitmap
|
||||||
|
|
||||||
|
@ -2719,6 +2756,14 @@ Rewrites the config file the server was started with.
|
||||||
|
|
||||||
String: OK or error msg.
|
String: OK or error msg.
|
||||||
|
|
||||||
|
### RESTORE key ttl value
|
||||||
|
|
||||||
|
Create a key associated with a value that is obtained by deserializing the provided serialized value (obtained via DUMP, LDUMP, HDUMP, SDUMP, ZDUMP).
|
||||||
|
|
||||||
|
If ttl is 0 the key is created without any expire, otherwise the specified expire time (in milliseconds) is set. But you must know that now the checking ttl accuracy is second.
|
||||||
|
|
||||||
|
RESTORE checks the RDB version and data checksum. If they don't match an error is returned.
|
||||||
|
|
||||||
## Transaction
|
## Transaction
|
||||||
|
|
||||||
### BEGIN
|
### BEGIN
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
package ledis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/siddontang/ledisdb/ledis/rdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
To support redis <-> ledisdb, the dump value format is the same as redis.
|
||||||
|
We will not support bitmap, and may add bit operations for kv later.
|
||||||
|
|
||||||
|
But you must know that we use int64 for zset score, not double.
|
||||||
|
Only support rdb version 6.
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (db *DB) Dump(key []byte) ([]byte, error) {
|
||||||
|
v, err := db.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if v == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rdb.Dump(rdb.String(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) LDump(key []byte) ([]byte, error) {
|
||||||
|
v, err := db.LRange(key, 0, -1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if len(v) == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rdb.Dump(rdb.List(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) HDump(key []byte) ([]byte, error) {
|
||||||
|
v, err := db.HGetAll(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if len(v) == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o := make(rdb.HashMap, len(v))
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
o[i].Field = v[i].Field
|
||||||
|
o[i].Value = v[i].Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return rdb.Dump(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) SDump(key []byte) ([]byte, error) {
|
||||||
|
v, err := db.SMembers(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if len(v) == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rdb.Dump(rdb.Set(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) ZDump(key []byte) ([]byte, error) {
|
||||||
|
v, err := db.ZRangeByScore(key, MinScore, MaxScore, 0, -1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if len(v) == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o := make(rdb.ZSet, len(v))
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
o[i].Member = v[i].Member
|
||||||
|
o[i].Score = float64(v[i].Score)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rdb.Dump(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) Restore(key []byte, ttl int64, data []byte) error {
|
||||||
|
d, err := rdb.DecodeDump(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//ttl is milliseconds, but we only support seconds
|
||||||
|
//later may support milliseconds
|
||||||
|
if ttl > 0 {
|
||||||
|
ttl = ttl / 1e3
|
||||||
|
if ttl == 0 {
|
||||||
|
ttl = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value := d.(type) {
|
||||||
|
case rdb.String:
|
||||||
|
if err = db.Set(key, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ttl > 0 {
|
||||||
|
if _, err = db.Expire(key, ttl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case rdb.HashMap:
|
||||||
|
fv := make([]FVPair, len(value))
|
||||||
|
for i := 0; i < len(value); i++ {
|
||||||
|
fv[i] = FVPair{Field: value[i].Field, Value: value[i].Value}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = db.HMset(key, fv...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ttl > 0 {
|
||||||
|
if _, err = db.HExpire(key, ttl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case rdb.List:
|
||||||
|
if _, err = db.RPush(key, value...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ttl > 0 {
|
||||||
|
if _, err = db.LExpire(key, ttl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case rdb.ZSet:
|
||||||
|
sp := make([]ScorePair, len(value))
|
||||||
|
for i := 0; i < len(value); i++ {
|
||||||
|
sp[i] = ScorePair{int64(value[i].Score), value[i].Member}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = db.ZAdd(key, sp...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ttl > 0 {
|
||||||
|
if _, err = db.ZExpire(key, ttl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case rdb.Set:
|
||||||
|
if _, err = db.SAdd(key, value...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ttl > 0 {
|
||||||
|
if _, err = db.SExpire(key, ttl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid data type %T", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package ledis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/siddontang/ledisdb/config"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMigrate(t *testing.T) {
|
||||||
|
cfg1 := config.NewConfigDefault()
|
||||||
|
cfg1.DataDir = "/tmp/test_ledisdb_migrate1"
|
||||||
|
os.RemoveAll(cfg1.DataDir)
|
||||||
|
|
||||||
|
defer os.RemoveAll(cfg1.DataDir)
|
||||||
|
|
||||||
|
l1, _ := Open(cfg1)
|
||||||
|
defer l1.Close()
|
||||||
|
|
||||||
|
cfg2 := config.NewConfigDefault()
|
||||||
|
cfg2.DataDir = "/tmp/test_ledisdb_migrate2"
|
||||||
|
os.RemoveAll(cfg2.DataDir)
|
||||||
|
|
||||||
|
defer os.RemoveAll(cfg2.DataDir)
|
||||||
|
|
||||||
|
l2, _ := Open(cfg2)
|
||||||
|
defer l2.Close()
|
||||||
|
|
||||||
|
db1, _ := l1.Select(0)
|
||||||
|
db2, _ := l2.Select(0)
|
||||||
|
|
||||||
|
key := []byte("a")
|
||||||
|
lkey := []byte("a")
|
||||||
|
hkey := []byte("a")
|
||||||
|
skey := []byte("a")
|
||||||
|
zkey := []byte("a")
|
||||||
|
value := []byte("1")
|
||||||
|
|
||||||
|
db1.Set(key, value)
|
||||||
|
|
||||||
|
if data, err := db1.Dump(key); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err := db2.Restore(key, 0, data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db1.RPush(lkey, []byte("1"), []byte("2"), []byte("3"))
|
||||||
|
|
||||||
|
if data, err := db1.LDump(lkey); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err := db2.Restore(lkey, 0, data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db1.SAdd(skey, []byte("1"), []byte("2"), []byte("3"))
|
||||||
|
|
||||||
|
if data, err := db1.SDump(skey); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err := db2.Restore(skey, 0, data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db1.HMset(hkey, FVPair{[]byte("a"), []byte("1")}, FVPair{[]byte("b"), []byte("2")}, FVPair{[]byte("c"), []byte("3")})
|
||||||
|
|
||||||
|
if data, err := db1.HDump(hkey); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err := db2.Restore(hkey, 0, data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db1.ZAdd(zkey, ScorePair{1, []byte("a")}, ScorePair{2, []byte("b")}, ScorePair{3, []byte("c")})
|
||||||
|
|
||||||
|
if data, err := db1.ZDump(zkey); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err := db2.Restore(zkey, 0, data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkLedisEqual(l1, l2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package rdb
|
||||||
|
|
||||||
|
// Copyright 2014 Wandoujia Inc. All Rights Reserved.
|
||||||
|
// Licensed under the MIT (MIT-LICENSE.txt) license.
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cupcake/rdb"
|
||||||
|
"github.com/cupcake/rdb/nopdecoder"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DecodeDump(p []byte) (interface{}, error) {
|
||||||
|
d := &decoder{}
|
||||||
|
if err := rdb.DecodeDump(p, 0, nil, 0, d); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.obj, d.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
nopdecoder.NopDecoder
|
||||||
|
obj interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) initObject(obj interface{}) {
|
||||||
|
if d.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if d.obj != nil {
|
||||||
|
d.err = fmt.Errorf("invalid object, init again")
|
||||||
|
} else {
|
||||||
|
d.obj = obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) Set(key, value []byte, expiry int64) {
|
||||||
|
d.initObject(String(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) StartHash(key []byte, length, expiry int64) {
|
||||||
|
d.initObject(HashMap(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) Hset(key, field, value []byte) {
|
||||||
|
if d.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch h := d.obj.(type) {
|
||||||
|
default:
|
||||||
|
d.err = fmt.Errorf("invalid object, not a hashmap")
|
||||||
|
case HashMap:
|
||||||
|
v := struct {
|
||||||
|
Field, Value []byte
|
||||||
|
}{
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
d.obj = append(h, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) StartSet(key []byte, cardinality, expiry int64) {
|
||||||
|
d.initObject(Set(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) Sadd(key, member []byte) {
|
||||||
|
if d.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch s := d.obj.(type) {
|
||||||
|
default:
|
||||||
|
d.err = fmt.Errorf("invalid object, not a set")
|
||||||
|
case Set:
|
||||||
|
d.obj = append(s, member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) StartList(key []byte, length, expiry int64) {
|
||||||
|
d.initObject(List(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) Rpush(key, value []byte) {
|
||||||
|
if d.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch l := d.obj.(type) {
|
||||||
|
default:
|
||||||
|
d.err = fmt.Errorf("invalid object, not a list")
|
||||||
|
case List:
|
||||||
|
d.obj = append(l, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) StartZSet(key []byte, cardinality, expiry int64) {
|
||||||
|
d.initObject(ZSet(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) Zadd(key []byte, score float64, member []byte) {
|
||||||
|
if d.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch z := d.obj.(type) {
|
||||||
|
default:
|
||||||
|
d.err = fmt.Errorf("invalid object, not a zset")
|
||||||
|
case ZSet:
|
||||||
|
v := struct {
|
||||||
|
Member []byte
|
||||||
|
Score float64
|
||||||
|
}{
|
||||||
|
member,
|
||||||
|
score,
|
||||||
|
}
|
||||||
|
d.obj = append(z, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type String []byte
|
||||||
|
type List [][]byte
|
||||||
|
type HashMap []struct {
|
||||||
|
Field, Value []byte
|
||||||
|
}
|
||||||
|
type Set [][]byte
|
||||||
|
type ZSet []struct {
|
||||||
|
Member []byte
|
||||||
|
Score float64
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package rdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/cupcake/rdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Dump(obj interface{}) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
e := rdb.NewEncoder(&buf)
|
||||||
|
|
||||||
|
switch v := obj.(type) {
|
||||||
|
case String:
|
||||||
|
e.EncodeType(rdb.TypeString)
|
||||||
|
e.EncodeString(v)
|
||||||
|
case HashMap:
|
||||||
|
e.EncodeType(rdb.TypeHash)
|
||||||
|
e.EncodeLength(uint32(len(v)))
|
||||||
|
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
e.EncodeString(v[i].Field)
|
||||||
|
e.EncodeString(v[i].Value)
|
||||||
|
}
|
||||||
|
case List:
|
||||||
|
e.EncodeType(rdb.TypeList)
|
||||||
|
e.EncodeLength(uint32(len(v)))
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
e.EncodeString(v[i])
|
||||||
|
}
|
||||||
|
case Set:
|
||||||
|
e.EncodeType(rdb.TypeSet)
|
||||||
|
e.EncodeLength(uint32(len(v)))
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
e.EncodeString(v[i])
|
||||||
|
}
|
||||||
|
case ZSet:
|
||||||
|
e.EncodeType(rdb.TypeZSet)
|
||||||
|
e.EncodeLength(uint32(len(v)))
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
e.EncodeString(v[i].Member)
|
||||||
|
e.EncodeFloat(v[i].Score)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid dump type %T", obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.EncodeDumpFooter()
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package rdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCodec(t *testing.T) {
|
||||||
|
testCodec(String("abc"), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCodec(obj interface{}, t *testing.T) {
|
||||||
|
b, err := Dump(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if o, err := DecodeDump(b); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if !reflect.DeepEqual(obj, o) {
|
||||||
|
t.Fatal("must equal")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Wandoujia Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -18,7 +18,7 @@ func checkLedisEqual(master *Ledis, slave *Ledis) error {
|
||||||
if v, err := slave.ldb.Get(key); err != nil {
|
if v, err := slave.ldb.Get(key); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !bytes.Equal(v, value) {
|
} else if !bytes.Equal(v, value) {
|
||||||
return fmt.Errorf("replication error %d != %d", len(v), len(value))
|
return fmt.Errorf("equal error at %q, %d != %d", key, len(v), len(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
We will not maintain bitmap anymore, and will add bit operations for kv type later.
|
||||||
|
Use your own risk.
|
||||||
|
*/
|
||||||
|
|
||||||
const (
|
const (
|
||||||
OPand uint8 = iota + 1
|
OPand uint8 = iota + 1
|
||||||
OPor
|
OPor
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/siddontang/ledisdb/ledis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dumpCommand(c *client) error {
|
||||||
|
if len(c.args) != 1 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
key := c.args[0]
|
||||||
|
if data, err := c.db.Dump(key); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.resp.writeBulk(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ldumpCommand(c *client) error {
|
||||||
|
if len(c.args) != 1 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
key := c.args[0]
|
||||||
|
if data, err := c.db.LDump(key); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.resp.writeBulk(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hdumpCommand(c *client) error {
|
||||||
|
if len(c.args) != 1 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
key := c.args[0]
|
||||||
|
if data, err := c.db.HDump(key); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.resp.writeBulk(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sdumpCommand(c *client) error {
|
||||||
|
if len(c.args) != 1 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
key := c.args[0]
|
||||||
|
if data, err := c.db.SDump(key); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.resp.writeBulk(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func zdumpCommand(c *client) error {
|
||||||
|
if len(c.args) != 1 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
key := c.args[0]
|
||||||
|
if data, err := c.db.ZDump(key); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.resp.writeBulk(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreCommand(c *client) error {
|
||||||
|
args := c.args
|
||||||
|
if len(args) != 3 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
key := args[0]
|
||||||
|
ttl, err := ledis.StrInt64(args[1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data := args[2]
|
||||||
|
|
||||||
|
if err = c.db.Restore(key, ttl, data); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.resp.writeStatus(OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("dump", dumpCommand)
|
||||||
|
register("ldump", ldumpCommand)
|
||||||
|
register("hdump", hdumpCommand)
|
||||||
|
register("sdump", sdumpCommand)
|
||||||
|
register("zdump", zdumpCommand)
|
||||||
|
register("restore", restoreCommand)
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/siddontang/ledisdb/client/go/ledis"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMigrate(t *testing.T) {
|
||||||
|
c := getTestConn()
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
_, err = c.Do("set", "mtest_a", "1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = c.Do("rpush", "mtest_la", "1", "2", "3")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = c.Do("hmset", "mtest_ha", "a", "1", "b", "2")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = c.Do("sadd", "mtest_sa", "1", "2", "3")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = c.Do("zadd", "mtest_za", 1, "a", 2, "b", 3, "c")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testMigrate(c, "dump", "mtest_a", t)
|
||||||
|
testMigrate(c, "ldump", "mtest_la", t)
|
||||||
|
testMigrate(c, "hdump", "mtest_ha", t)
|
||||||
|
testMigrate(c, "sdump", "mtest_sa", t)
|
||||||
|
testMigrate(c, "zdump", "mtest_za", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMigrate(c *ledis.Conn, dump string, key string, t *testing.T) {
|
||||||
|
if data, err := ledis.Bytes(c.Do(dump, key)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if _, err := c.Do("restore", key, 0, data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue