diff --git a/README.md b/README.md index 724ad21..30ff2c7 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # LedisDB -Ledisdb is a high performance NoSQL like Redis written by go. It supports some advanced data structure like kv, list, hash, zset, bitmap, and may be alternative for Redis. +Ledisdb is a high performance NoSQL like Redis written by go. It supports some advanced data structure like kv, list, hash, zset, bitmap,set, and may be alternative for Redis. LedisDB now supports multiple databases as backend to store data, you can test and choose the proper one for you. ## Features -+ Rich advanced data structure: KV, List, Hash, ZSet, Bitmap. ++ Rich advanced data structure: KV, List, Hash, ZSet, Bitmap, Set. + Stores lots of data, over the memory limit. + Various backend database to use: LevelDB, goleveldb, LMDB, RocksDB, BoltDB, HyperLevelDB. + Supports expiration and ttl. diff --git a/cmd/ledis-cli/const.go b/cmd/ledis-cli/const.go index 4e89d4d..ab76d34 100644 --- a/cmd/ledis-cli/const.go +++ b/cmd/ledis-cli/const.go @@ -1,56 +1,96 @@ +//This file was generated by ./generate.py on Fri Aug 15 2014 16:40:03 +0800 package main var helpCommands = [][]string{ + {"BCOUNT", "key [start end]", "Bitmap"}, + {"BDELETE", "key", "ZSet"}, + {"BEXPIRE", "key seconds", "Bitmap"}, + {"BEXPIREAT", "key timestamp", "Bitmap"}, + {"BGET", "key", "Bitmap"}, + {"BGETBIT", "key offset", "Bitmap"}, + {"BMSETBIT", "key offset value [offset value ...]", "Bitmap"}, + {"BOPT", "operation destkey key [key ...]", "Bitmap"}, + {"BPERSIST", "key", "Bitmap"}, + {"BSETBIT", "key offset value", "Bitmap"}, + {"BTTL", "key", "Bitmap"}, {"DECR", "key", "KV"}, {"DECRBY", "key decrement", "KV"}, {"DEL", "key [key ...]", "KV"}, + {"ECHO", "message", "Server"}, {"EXISTS", "key", "KV"}, {"EXPIRE", "key seconds", "KV"}, {"EXPIREAT", "key timestamp", "KV"}, + {"FULLSYNC", "-", "Replication"}, {"GET", "key", "KV"}, {"GETSET", " key value", "KV"}, - {"INCR", "key", "KV"}, - {"INCRBY", "key increment", "KV"}, - {"MGET", "key [key ...]", "KV"}, - {"MSET", "key value [key value ...]", "KV"}, - {"SET", "key value", "KV"}, - {"SETNX", "key value", "KV"}, - {"TTL", "key", "KV"}, - {"PERSIST", "key", "KV"}, + {"HCLEAR", "key", "Hash"}, {"HDEL", "key field [field ...]", "Hash"}, {"HEXISTS", "key field", "Hash"}, + {"HEXPIRE", "key seconds", "Hash"}, + {"HEXPIREAT", "key timestamp", "Hash"}, {"HGET", "key field", "Hash"}, {"HGETALL", "key", "Hash"}, {"HINCRBY", "key field increment", "Hash"}, {"HKEYS", "key", "Hash"}, {"HLEN", "key", "Hash"}, + {"HMCLEAR", "key [key ...]", "Hash"}, {"HMGET", "key field [field ...]", "Hash"}, {"HMSET", "key field value [field value ...]", "Hash"}, - {"HSET", "key field value", "Hash"}, - {"HVALS", "key", "Hash"}, - {"HCLEAR", "key", "Hash"}, - {"HMCLEAR", "key [key ...]", "Hash"}, - {"HEXPIRE", "key seconds", "Hash"}, - {"HEXPIREAT", "key timestamp", "Hash"}, - {"HTTL", "key", "Hash"}, {"HPERSIST", "key", "Hash"}, + {"HSET", "key field value", "Hash"}, + {"HTTL", "key", "Hash"}, + {"HVALS", "key", "Hash"}, + {"INCR", "key", "KV"}, + {"INCRBY", "key increment", "KV"}, + {"LCLEAR", "key", "List"}, + {"LEXPIRE", "key seconds", "List"}, + {"LEXPIREAT", "key timestamp", "List"}, {"LINDEX", "key index", "List"}, {"LLEN", "key", "List"}, + {"LMCLEAR", "key [key ...]", "List"}, + {"LPERSIST", "key", "List"}, {"LPOP", "key", "List"}, {"LPUSH", "key value [value ...]", "List"}, {"LRANGE", "key start stop", "List"}, + {"LTTL", "key", "List"}, + {"MGET", "key [key ...]", "KV"}, + {"MSET", "key value [key value ...]", "KV"}, + {"PERSIST", "key", "KV"}, + {"PING", "-", "Server"}, {"RPOP", "key", "List"}, {"RPUSH", "key value [value ...]", "List"}, - {"LCLEAR", "key", "List"}, - {"LMCLEAR", "key [key ...]", "List"}, - {"LEXPIRE", "key seconds", "List"}, - {"LEXPIREAT", "key timestamp", "List"}, - {"LTTL", "key", "List"}, - {"LPERSIST", "key", "List"}, + {"SADD", "key member [member ...]", "Set"}, + {"SCARD", "key", "Set"}, + {"SCLEAR", "key", "Set"}, + {"SDIFF", "key [key ...]", "Set"}, + {"SDIFFSTORE", "destination key [key ...]", "Set"}, + {"SELECT", "index", "Server"}, + {"SET", "key value", "KV"}, + {"SETNX", "key value", "KV"}, + {"SEXPIRE", "key seconds", "Set"}, + {"SEXPIREAT", "key timestamp", "Set"}, + {"SINTER", "key [key ...]", "Set"}, + {"SINTERSTORE", "destination key [key ...]", "Set"}, + {"SISMEMBER", "key member", "Set"}, + {"SLAVEOF", "host port", "Replication"}, + {"SMCLEAR", "key [key ...]", "Set"}, + {"SMEMBERS", "key", "Set"}, + {"SPERSIST", "key", "Set"}, + {"SREM", "key member [member ...]", "Set"}, + {"STTL", "key", "Set"}, + {"SUNION", "key [key ...]", "Set"}, + {"SUNIONSTORE", "destination key [key ...]", "Set"}, + {"SYNC", "index offset", "Replication"}, + {"TTL", "key", "KV"}, {"ZADD", "key score member [score member ...]", "ZSet"}, {"ZCARD", "key", "ZSet"}, + {"ZCLEAR", "key", "ZSet"}, {"ZCOUNT", "key min max", "ZSet"}, + {"ZEXPIRE", "key seconds", "ZSet"}, + {"ZEXPIREAT", "key timestamp", "ZSet"}, {"ZINCRBY", "key increment member", "ZSet"}, + {"ZMCLEAR", "key [key ...]", "ZSet"}, + {"ZPERSIST", "key", "ZSet"}, {"ZRANGE", "key start stop [WITHSCORES]", "ZSet"}, {"ZRANGEBYSCORE", "key min max [WITHSCORES] [LIMIT offset count]", "ZSet"}, {"ZRANK", "key member", "ZSet"}, @@ -61,27 +101,5 @@ var helpCommands = [][]string{ {"ZREVRANGEBYSCORE", "key max min [WITHSCORES][LIMIT offset count]", "ZSet"}, {"ZREVRANK", "key member", "ZSet"}, {"ZSCORE", "key member", "ZSet"}, - {"ZCLEAR", "key", "ZSet"}, - {"ZMCLEAR", "key [key ...]", "ZSet"}, - {"ZEXPIRE", "key seconds", "ZSet"}, - {"ZEXPIREAT", "key timestamp", "ZSet"}, {"ZTTL", "key", "ZSet"}, - {"ZPERSIST", "key", "ZSet"}, - {"BDELETE", "key", "ZSet"}, - {"BGET", "key", "Bitmap"}, - {"BGETBIT", "key offset", "Bitmap"}, - {"BSETBIT", "key offset value", "Bitmap"}, - {"BMSETBIT", "key offset value [offset value ...]", "Bitmap"}, - {"BOPT", "operation destkey key [key ...]", "Bitmap"}, - {"BCOUNT", "key [start end]", "Bitmap"}, - {"BEXPIRE", "key seconds", "Bitmap"}, - {"BEXPIREAT", "key timestamp", "Bitmap"}, - {"BTTL", "key", "Bitmap"}, - {"BPERSIST", "key", "Bitmap"}, - {"SLAVEOF", "host port", "Replication"}, - {"FULLSYNC", "-", "Replication"}, - {"SYNC", "index offset", "Replication"}, - {"PING", "-", "Server"}, - {"ECHO", "message", "Server"}, - {"SELECT", "index", "Server"}, } diff --git a/doc/commands.json b/doc/commands.json index c51f304..5ff7070 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -310,6 +310,91 @@ "group": "Replication", "readonly": false }, + "SADD" :{ + "arguments": "key member [member ...]", + "group": "Set", + "readonly": false + }, + "SCARD": { + "arguments": "key", + "group": "Set", + "readonly": true + }, + "SDIFF": { + "arguments": "key [key ...]", + "group": "Set", + "readonly": true + }, + "SDIFFSTORE": { + "arguments": "destination key [key ...]", + "group": "Set", + "readonly": false + }, + "SINTER": { + "arguments": "key [key ...]", + "group": "Set", + "readonly": true + }, + "SINTERSTORE": { + "arguments": "destination key [key ...]", + "group": "Set", + "readonly": false + }, + "SISMEMBER": { + "arguments": "key member", + "group": "Set", + "readonly": true + }, + "SMEMBERS": { + "arguments": "key", + "group": "Set", + "readonly": true + }, + "SREM": { + "arguments": "key member [member ...]", + "group": "Set", + "readonly": false + }, + "SUNION": { + "arguments": "key [key ...]", + "group": "Set", + "readonly": true + }, + "SUNIONSTORE": { + "arguments": "destination key [key ...]", + "group": "Set", + "readonly": false + }, + "SCLEAR": { + "arguments": "key", + "group": "Set", + "readonly": false + }, + "SMCLEAR": { + "arguments": "key [key ...]", + "group": "Set", + "readonly": false + }, + "SEXPIRE": { + "arguments": "key seconds", + "group": "Set", + "readonly": false + }, + "SEXPIREAT": { + "arguments": "key timestamp", + "group": "Set", + "readonly": false + }, + "STTL": { + "arguments": "key", + "group": "Set", + "readonly": true + }, + "SPERSIST": { + "arguments": "key", + "group": "Set", + "readonly": false + }, "TTL": { "arguments": "key", "group": "KV", diff --git a/doc/commands.md b/doc/commands.md index b4855e7..9880826 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -58,6 +58,25 @@ Table of Contents - [LEXPIREAT key timestamp](#lexpireat-key-timestamp) - [LTTL key](#lttl-key) - [LPERSIST key](#lpersist-key) +- [Set](#set) + - [SADD key member [member ...]](#sadd-key-member-member-) + - [SCARD key](#scard-key) + - [SDIFF key [key ...]](#sdiff-key-key-) + - [SDIFFSTORE destination key [key ...]](#sdiffstore-destination-key-key-) + - [SINTER key [key ...]](#sinter-key-key-) + - [SINTERSTORE destination key [key ...]](#sinterstore-destination-key-key-) + - [SISMEMBER key member](#sismember-key-member) + - [SMEMBERS key](#smembers-key) + - [SREM key member [member]](#srem-key-member-member-) + - [SUNION key [key ...]](#sunion-key-key-) + - [SUNIONSTORE destination key [key ...]](#sunionstore-destination-key-key-) + - [SCLEAR key](#sclear-key) + - [SMCLEAR key [key...]](#smclear-key-key) + - [SEXPIRE key seconds](#sexpire-key-seconds) + - [SEXPIREAT key timestamp](#sexpireat-key-timestamp) + - [STTL key](#sttl-key) + - [SPERSIST key](#spersist-key) + - [ZSet](#zset) - [ZADD key score member [score member ...]](#zadd-key-score-member-score-member-) - [ZCARD key](#zcard-key) @@ -665,7 +684,7 @@ ledis> HVALS myhash ### HCLEAR key -Deletes the specified hash keys +Deletes the specified hash key **Return value** @@ -1093,6 +1112,427 @@ ledis> LPERSIST b ``` +## Set + +### SADD key member [member ...] +Add the specified members to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members. + +**Return value** + +int64: the number of elements that were added to the set, not including all the elements already present into the set. + +**Examples** + +``` +ledis> SADD myset hello +(integer) 1 +ledis> SADD myset world +(integer) 1 +ledis> SADD myset hello +(integer) 0 +ledis> SMEMBERS myset +1) "hello" +2) "world" +``` + + +### SCARD key + +Returns the set cardinality (number of elements) of the set stored at key. + +**Return value** + +int64: the cardinality (number of elements) of the set, or 0 if key does not exist. + +**Examples** + +``` +ledis> SADD myset hello +(integer) 1 +ledis> SADD myset world +(integer) 1 +ledis> SADD myset hello +(integer) 0 +ledis> SCARD myset +(integer) 2 +``` + + +### SDIFF key [key ...] +Returns the members of the set resulting from the difference between the first set and all the successive sets. +For example: + +``` +key1 = {a,b,c,d} +key2 = {c} +key3 = {a,c,e} +SDIFF key1 key2 key3 = {b,d} +``` + +Keys that do not exist are considered to be empty sets. + + +**Return value** + +bulk: list with members of the resulting set. + +**Examples** + +``` +ledis> SADD key1 a b c +(integer) 3 +ledis> SADD key2 c d e +(integer) 3 +ledis> SDIFF key1 key2 +1) "a" +2) "b" +ledis> SDIFF key2 key1 +1) "d" +2) "e" +``` + +### SDIFFSTORE destination key [key ...] +This command is equal to `SDIFF`, but instead of returning the resulting set, it is stored in destination. +If destination already exists, it is overwritten. + +**Return value** + +int64: the number of elements in the resulting set. + +**Examples** + +``` +ledis> SADD key1 a b c +(integer) 3 +ledis> SADD key2 c d e +(integer) 3 +ledis> SDIFF key1 key2 +1) "a" +2) "b" +ledis> SDIFFSTORE key key1 key2 +(integer) 2 +ledis> SMEMBERS key +1) "a" +2) "b" +``` + +### SINTER key [key ...] + +Returns the members of the set resulting from the intersection of all the given sets. +For example: + +``` +key1 = {a,b,c,d} +key2 = {c} +key3 = {a,c,e} +SINTER key1 key2 key3 = {c} +``` + +Keys that do not exist are considered to be empty sets. With one of the keys being an empty set, the resulting set is also empty (since set intersection with an empty set always results in an empty set). + +**Return value** + +bulk: list with members of the resulting set. + +**Examples** + +``` +ledis> SADD key1 a b c +(integer) 3 +ledis> SADD key2 c d e +(integer) 3 +ledis> SINTER key1 key2 +1) "c" +ledis> SINTER key2 key_empty +(nil) +``` + + +### SINTERSTORE destination key [key ...] + +This command is equal to `SINTER`, but instead of returning the resulting set, it is stored in destination. +If destination already exists, it is overwritten. + +**Return value** + +int64: the number of elements in the resulting set. + +**Examples** + +``` +ledis> SADD key1 a b c +(integer) 3 +ledis> SADD key2 c d e +(integer) 3 +ledis> SINTERSTORE key key1 key2 +(integer) 1 +ledis> SMEMBERS key +1) "c" +``` + + +### SISMEMBER key member +Returns if member is a member of the set stored at key. + +**Return value** + +Int64 reply, specifically: + +- 1 if the element is a member of the set. +- 0 if the element is not a member of the set, or if key does not exist. + +**Examples** + +``` +ledis> SADD myset hello +(integer) 1 +ledis> SISMEMBER myset hello +(integer) 1 +ledis> SISMEMBER myset hell +(integer) 0 +``` + +### SMEMBERS key +Returns all the members of the set value stored at key. +This has the same effect as running `SINTER` with one argument key. + +**Return value** + +bulk: all elements of the set. + +**Examples** + +``` +ledis> SADD myset hello +(integer) 1 +ledis> SADD myset world +(integer) 1 +ledis> SMEMBERS myset +1) "hello" +2) "world" +``` + +### SREM key member [member ...] + +Remove the specified members from the set stored at key. Specified members that are not a member of this set are ignored. If key does not exist, it is treated as an empty set and this command returns 0. + +**Return value** + +int64: the number of members that were removed from the set, not including non existing members. + +**Examples** + +``` +ledis> SADD myset one +(integer) 1 +ledis> SADD myset two +(integer) 1 +ledis> SADD myset three +(integer) 1 +ledis> SREM myset one +(integer) 1 +ledis> SREM myset four +(integer) 0 +ledis> SMEMBERS myset +1) "three" +2) "two" +``` + +### SUNION key [key ...] + +Returns the members of the set resulting from the union of all the given sets. +For example: + +``` +key1 = {a,b,c,d} +key2 = {c} +key3 = {a,c,e} +SUNION key1 key2 key3 = {a,b,c,d,e} +``` +Keys that do not exist are considered to be empty sets. + + +**Return value** + +bulk: list with members of the resulting set. + +**Examples** + +``` +ledis> SMEMBERS key1 +1) "a" +2) "b" +3) "c" +ledis> SMEMBERS key2 +1) "c" +2) "d" +3) "e" +ledis> SUNION key1 key2 +1) "a" +2) "b" +3) "c" +4) "d" +5) "e" +``` + +### SUNIONSTORE destination key [key] + +This command is equal to SUNION, but instead of returning the resulting set, it is stored in destination. +If destination already exists, it is overwritten. + +**Return value** + +int64: the number of elements in the resulting set. + +**Examples** + +``` +ledis> SMEMBERS key1 +1) "a" +2) "b" +3) "c" +ledis> SMEMBERS key2 +1) "c" +2) "d" +3) "e" +ledis> SUNIONSTORE key key1 key2 +(integer) 5 +ledis> SMEMBERS key +1) "a" +2) "b" +3) "c" +4) "d" +5) "e" +``` + + +### SCLEAR key + +Deletes the specified set key + +**Return value** + +int64: the number of fields in the hash stored at key + +**Examples** + +``` +ledis> SMEMBERS key +1) "a" +2) "b" +3) "c" +4) "d" +5) "e" +ledis> SCLEAR key +(integer) 5 +``` + +### SMCLEAR key [key ...] + +Deletes the specified set keys. + +**Return value** + +int64: the number of input keys + +**Examples** + +``` +ledis> SMCLEAR key1 key2 +(integer) 2 +ledis> SMCLEAR em1 em2 +(integer) 2 +``` + +### SEXPIRE key seconds + +Sets a set key’s time to live in seconds, like expire similarly. + +**Return value** + +int64: + +- 1 if the timeout was set +- 0 if key does not exist or the timeout could not be set + +**Examples** + +``` +ledis> SADD key 1 2 +(integer) 2 +ledis> SEXPIRE key 100 +(integer) 1 +ledis> STTL key +(integer) 95 +``` + + +### SEXPIREAT key timestamp + +Sets the expiration for a set key as a unix timestamp, like expireat similarly. + +**Return value** + +int64: + +- 1 if the timeout was set +- 0 if key does not exist or the timeout could not be set + +**Examples** + +``` +ledis> SADD key 1 2 +(integer) 2 +ledis> SEXPIREAT key 1408094999 +(integer) 1 +ledis> STTL key +(integer) 908 +``` + + +### STTL key + +Returns the remaining time to live of a key that has a timeout. If the key was not set a timeout, -1 returns. + +**Return value** + +int64: TTL in seconds + +**Examples** + +``` +ledis> SADD key 1 2 +(integer) 2 +ledis> SEXPIREAT key 1408094999 +(integer) 1 +ledis> STTL key +(integer) 908 +``` + + +### SPERSIST key +Remove the expiration from a set key, like persist similarly. Remove the existing timeout on key. + +**Return value** + +int64: + +- 1 if the timeout was removed +- 0 if key does not exist or does not have an timeout + +**Examples** + +``` +ledis> SEXPIREAT key 1408094999 +(integer) 1 +ledis> STTL key +(integer) 908 +ledis> SPERSIST key +(integer) 1 +ledis> STTL key +(integer) -1 +``` + ## ZSet ### ZADD key score member [score member ...] @@ -1114,13 +1554,13 @@ The number of elements added to the sorted sets, **not** including elements alre **Examples** ``` -ledis> ZADD myset 1 'one' +ledis> ZADD myzset 1 'one' (integer) 1 -ledis> ZADD myset 1 'uno' +ledis> ZADD myzset 1 'uno' (integer) 1 -ledis> ZADD myset 2 'two' 3 'three' +ledis> ZADD myzset 2 'two' 3 'three' (integer) 2 -ledis> ZRANGE myset 0 -1 WITHSCORES +ledis> ZRANGE myzset 0 -1 WITHSCORES 1) "one" 2) "1" 3) "uno" @@ -1141,13 +1581,13 @@ int64: the cardinality (number of elements) of the sorted set, or `0` if key doe **Examples** ``` -edis > ZADD myset 1 'one' +edis > ZADD myzset 1 'one' (integer) 1 -ledis> ZADD myset 1 'uno' +ledis> ZADD myzset 1 'uno' (integer) 1 -ledis> ZADD myset 2 'two' 3 'three' +ledis> ZADD myzset 2 'two' 3 'three' (integer) 2 -ledis> ZRANGE myset 0 -1 WITHSCORES +ledis> ZRANGE myzset 0 -1 WITHSCORES 1) "one" 2) "1" 3) "uno" @@ -1156,7 +1596,7 @@ ledis> ZRANGE myset 0 -1 WITHSCORES 6) "2" 7) "three" 8) "3" -ledis> ZCARD myset +ledis> ZCARD myzset (integer) 4 ``` @@ -1171,13 +1611,13 @@ int64: the number of elements in the specified score range. **Examples** ``` -ledis> ZADD myset 1 'one' +ledis> ZADD myzset 1 'one' (integer) 1 -ledis> ZADD myset 1 'uno' +ledis> ZADD myzset 1 'uno' (integer) 1 -ledis> ZADD myset 2 'two' 3 'three' +ledis> ZADD myzset 2 'two' 3 'three' (integer) 2 -ledis> ZRANGE myset 0 -1 WITHSCORES +ledis> ZRANGE myzset 0 -1 WITHSCORES 1) "one" 2) "1" 3) "uno" @@ -1186,9 +1626,9 @@ ledis> ZRANGE myset 0 -1 WITHSCORES 6) "2" 7) "three" 8) "3" -ledis> ZCOUNT myset -inf +inf +ledis> ZCOUNT myzset -inf +inf (integer) 4 -ledis> ZCOUNT myset (1 3 +ledis> ZCOUNT myzset (1 3 (integer) 2 ``` @@ -1205,13 +1645,13 @@ bulk: the new score of member (an int64 number), represented as string. **Examples** ``` -ledis> ZADD myset 1 'one' +ledis> ZADD myzset 1 'one' (integer) 1 -ledis> ZADD myset 2 'two' +ledis> ZADD myzset 2 'two' (integer) 1 -ledis> ZINCRBY myset 2 'one' +ledis> ZINCRBY myzset 2 'one' 3 -ledis> ZRANGE myset 0 -1 WITHSCORES +ledis> ZRANGE myzset 0 -1 WITHSCORES 1) "two" 2) "2" 3) "one" @@ -1228,19 +1668,19 @@ array: list of elements in the specified range (optionally with their scores). **Examples** ``` -ledis> ZADD myset 1 'one' +ledis> ZADD myzset 1 'one' (integer) 1 -ledis> ZADD myset 2 'two' +ledis> ZADD myzset 2 'two' (integer) 1 -ledis> ZADD myset 3 'three' +ledis> ZADD myzset 3 'three' (integer) 1 -ledis> ZRANGE myset 0 -1 +ledis> ZRANGE myzset 0 -1 1) "one" 2) "two" 3) "three" -ledis> ZRANGE myset 2 3 +ledis> ZRANGE myzset 2 3 1) "three" -ledis> ZRANGE myset -2 -1 +ledis> ZRANGE myzset -2 -1 1) "two" 2) "three" ``` @@ -1340,16 +1780,16 @@ The number of members removed from the sorted set, not including non existing me **Examples** ``` -ledis> ZADD myset 1 one 2 two 3 three 4 four +ledis> ZADD myzset 1 one 2 two 3 three 4 four (integer) 3 -ledis> ZRANGE myset 0 -1 +ledis> ZRANGE myzset 0 -1 1) "one" 2) "two" 3) "three" 4) "four" -ledis> ZREM myset three +ledis> ZREM myzset three (integer) 1 -ledis> ZREM myset one four three +ledis> ZREM myzset one four three (integer) 2 ``` @@ -1363,11 +1803,11 @@ int64: the number of elements removed. **Examples** ``` -ledis> ZADD myset 1 one 2 two 3 three 4 four +ledis> ZADD myzset 1 one 2 two 3 three 4 four (integer) 3 -ledis> ZREMRANGEBYRANK myset 0 2 +ledis> ZREMRANGEBYRANK myzset 0 2 (integer) 3 -ledis> ZRANGE myset 0 -1 WITHSCORES +ledis> ZRANGE myzset 0 -1 WITHSCORES 1) "four" 2) "4" ``` @@ -1383,11 +1823,11 @@ int64: the number of elements removed. **Examples** ``` -ledis> ZADD myset 1 one 2 two 3 three 4 four +ledis> ZADD myzset 1 one 2 two 3 three 4 four (integer) 4 -ledis> ZREMRANGEBYSCORE myset -inf (2 +ledis> ZREMRANGEBYSCORE myzset -inf (2 (integer) 1 -ledis> ZRANGE myset 0 -1 WITHSCORES +ledis> ZRANGE myzset 0 -1 WITHSCORES 1) "two" 2) "2" 3) "three" @@ -1407,9 +1847,9 @@ array: list of elements in the specified range (optionally with their scores). **Examples** ``` -ledis> ZADD myset 1 one 2 two 3 three 4 four +ledis> ZADD myzset 1 one 2 two 3 three 4 four (integer) 4 -ledis> ZREVRANGE myset 0 -1 +ledis> ZREVRANGE myzset 0 -1 1) "four" 2) "three" 3) "two" @@ -1428,21 +1868,21 @@ array: list of elements in the specified score range (optionally with their scor **Examples** ``` -ledis> ZADD myset 1 one 2 two 3 three 4 four +ledis> ZADD myzset 1 one 2 two 3 three 4 four (integer) 4 -ledis> ZREVRANGEBYSCORE myset +inf -inf +ledis> ZREVRANGEBYSCORE myzset +inf -inf 1) "four" 2) "three" 3) "two" 4) "one" -ledis> ZREVRANGEBYSCORE myset 2 1 +ledis> ZREVRANGEBYSCORE myzset 2 1 1) "two" 2) "one" -ledis> ZREVRANGEBYSCORE myset 2 (1 +ledis> ZREVRANGEBYSCORE myzset 2 (1 1) "two" -ledis> ZREVRANGEBYSCORE myset (2 (1 +ledis> ZREVRANGEBYSCORE myzset (2 (1 (empty list or set) -ledis> ZREVRANGEBYSCORE myset +inf -inf WITHSCORES LIMIT 1 2 +ledis> ZREVRANGEBYSCORE myzset +inf -inf WITHSCORES LIMIT 1 2 1) "three" 2) "3" 3) "two" @@ -1462,13 +1902,13 @@ Use ZRANK to get the rank of an element with the scores ordered from low to high **Examples** ``` -ledis> ZADD myset 1 one +ledis> ZADD myzset 1 one (integer) 1 -ledis> ZADD myset 2 two +ledis> ZADD myzset 2 two (integer) 1 -ledis> ZREVRANK myset one +ledis> ZREVRANK myzset one (integer) 1 -ledis> ZREVRANK myset three +ledis> ZREVRANK myzset three (nil) ``` @@ -1484,9 +1924,9 @@ bulk: the score of member (an `int64` number), represented as string. **Examples** ``` -ledis> ZADD myset 1 'one' +ledis> ZADD myzset 1 'one' (integer) 1 -ledis> ZSCORE myset 'one' +ledis> ZSCORE myzset 'one' 1 ``` @@ -1500,17 +1940,17 @@ int64: the number of members in the zset stored at key **Examples** ``` -ledis> ZADD myset 1 'one' +ledis> ZADD myzset 1 'one' (integer) 1 -ledis> ZADD myset 2 'two' +ledis> ZADD myzset 2 'two' (integer) 1 -ledis> ZADD myset 3 'three' +ledis> ZADD myzset 3 'three' (integer) 1 -ledis> ZRANGE myset 0 -1 +ledis> ZRANGE myzset 0 -1 1) "one" 2) "two" 3) "three" -ledis> ZCLEAR myset +ledis> ZCLEAR myzset (integer) 3 ``` @@ -1524,11 +1964,11 @@ int64: the number of input keys **Examples** ``` -ledis> ZADD myset1 1 'one' +ledis> ZADD myzset1 1 'one' (integer) 1 -ledis> ZADD myset2 2 'two' +ledis> ZADD myzset2 2 'two' (integer) 1 -ledis> ZMCLEAR myset1 myset2 +ledis> ZMCLEAR myzset1 myzset2 (integer) 2 ``` @@ -1547,17 +1987,17 @@ int64: **Examples** ``` -ledis> ZADD myset 1 'one' +ledis> ZADD myzset 1 'one' (integer) 1 -ledis> ZEXPIRE myset 100 +ledis> ZEXPIRE myzset 100 (integer) 1 -ledis> ZTTL myset +ledis> ZTTL myzset (integer) 97 -ledis> ZPERSIST myset +ledis> ZPERSIST myzset (integer) 1 ledis> ZTTL mset (integer) -1 -ledis> ZEXPIRE myset1 100 +ledis> ZEXPIRE myzset1 100 (integer) 0 ``` @@ -1574,17 +2014,17 @@ int64: **Examples** ``` -ledis> ZADD myset 1 'one' +ledis> ZADD myzset 1 'one' (integer) 1 -ledis> ZEXPIREAT myset 1404149999 +ledis> ZEXPIREAT myzset 1404149999 (integer) 1 -ledis> ZTTL myset +ledis> ZTTL myzset (integer) 7155 -ledis> ZPERSIST myset +ledis> ZPERSIST myzset (integer) 1 ledis> ZTTL mset (integer) -1 -ledis> ZEXPIREAT myset1 1404149999 +ledis> ZEXPIREAT myzset1 1404149999 (integer) 0 ``` @@ -1599,13 +2039,13 @@ int64: TTL in seconds **Examples** ``` -ledis> ZADD myset 1 'one' +ledis> ZADD myzset 1 'one' (integer) 1 -ledis> ZEXPIRE myset 100 +ledis> ZEXPIRE myzset 100 (integer) 1 -ledis> ZTTL myset +ledis> ZTTL myzset (integer) 97 -ledis> ZTTL myset2 +ledis> ZTTL myzset2 (integer) -1 ``` @@ -1622,13 +2062,13 @@ int64: **Examples** ``` -ledis> ZADD myset 1 'one' +ledis> ZADD myzset 1 'one' (integer) 1 -ledis> ZEXPIRE myset 100 +ledis> ZEXPIRE myzset 100 (integer) 1 -ledis> ZTTL myset +ledis> ZTTL myzset (integer) 97 -ledis> ZPERSIST myset +ledis> ZPERSIST myzset (integer) 1 ledis> ZTTL mset (integer) -1 diff --git a/ledis/const.go b/ledis/const.go index 9b2daaf..ef416de 100644 --- a/ledis/const.go +++ b/ledis/const.go @@ -16,6 +16,8 @@ const ( ZScoreType byte = 8 BitType byte = 9 BitMetaType byte = 10 + SetType byte = 11 + SSizeType byte = 12 maxDataType byte = 100 @@ -35,6 +37,8 @@ var ( ZScoreType: "zscore", BitType: "bit", BitMetaType: "bitmeta", + SetType: "set", + SSizeType: "ssize", ExpTimeType: "exptime", ExpMetaType: "expmeta", } @@ -48,6 +52,7 @@ var ( errKeySize = errors.New("invalid key size") errValueSize = errors.New("invalid value size") errHashFieldSize = errors.New("invalid hash field size") + errSetMemberSize = errors.New("invalid set member size") errZSetMemberSize = errors.New("invalid zset member size") errExpireValue = errors.New("invalid expire value") ) @@ -65,6 +70,9 @@ const ( //max zset member size MaxZSetMemberSize int = 1024 + //max set member size + MaxSetMemberSize int = 1024 + //max value size MaxValueSize int = 10 * 1024 * 1024 ) diff --git a/ledis/ledis.go b/ledis/ledis.go index 4c20736..65ab1ec 100644 --- a/ledis/ledis.go +++ b/ledis/ledis.go @@ -21,6 +21,7 @@ type DB struct { hashTx *tx zsetTx *tx binTx *tx + setTx *tx } type Ledis struct { @@ -88,6 +89,7 @@ func newDB(l *Ledis, index uint8) *DB { d.hashTx = newTx(l) d.zsetTx = newTx(l) d.binTx = newTx(l) + d.setTx = newTx(l) return d } diff --git a/ledis/ledis_db.go b/ledis/ledis_db.go index 7d58054..1bd9a54 100644 --- a/ledis/ledis_db.go +++ b/ledis/ledis_db.go @@ -10,7 +10,8 @@ func (db *DB) FlushAll() (drop int64, err error) { db.lFlush, db.hFlush, db.zFlush, - db.bFlush} + db.bFlush, + db.sFlush} for _, flush := range all { if n, e := flush(); e != nil { diff --git a/ledis/t_set.go b/ledis/t_set.go new file mode 100644 index 0000000..51458bb --- /dev/null +++ b/ledis/t_set.go @@ -0,0 +1,609 @@ +package ledis + +import ( + "encoding/binary" + "errors" + "github.com/siddontang/ledisdb/store" + "time" +) + +var errSetKey = errors.New("invalid set key") +var errSSizeKey = errors.New("invalid ssize key") + +const ( + setStartSep byte = ':' + setStopSep byte = setStartSep + 1 + UnionType byte = 51 + DiffType byte = 52 + InterType byte = 53 +) + +func checkSetKMSize(key []byte, member []byte) error { + if len(key) > MaxKeySize || len(key) == 0 { + return errKeySize + } else if len(member) > MaxSetMemberSize || len(member) == 0 { + return errSetMemberSize + } + return nil +} + +func (db *DB) sEncodeSizeKey(key []byte) []byte { + buf := make([]byte, len(key)+2) + + buf[0] = db.index + buf[1] = SSizeType + + copy(buf[2:], key) + return buf +} + +func (db *DB) sDecodeSizeKey(ek []byte) ([]byte, error) { + if len(ek) < 2 || ek[0] != db.index || ek[1] != SSizeType { + return nil, errSSizeKey + } + + return ek[2:], nil +} + +func (db *DB) sEncodeSetKey(key []byte, member []byte) []byte { + buf := make([]byte, len(key)+len(member)+1+1+2+1) + + pos := 0 + buf[pos] = db.index + pos++ + buf[pos] = SetType + pos++ + + binary.BigEndian.PutUint16(buf[pos:], uint16(len(key))) + pos += 2 + + copy(buf[pos:], key) + pos += len(key) + + buf[pos] = setStartSep + pos++ + copy(buf[pos:], member) + + return buf +} + +func (db *DB) sDecodeSetKey(ek []byte) ([]byte, []byte, error) { + if len(ek) < 5 || ek[0] != db.index || ek[1] != SetType { + return nil, nil, errSetKey + } + + pos := 2 + keyLen := int(binary.BigEndian.Uint16(ek[pos:])) + pos += 2 + + if keyLen+5 > len(ek) { + return nil, nil, errSetKey + } + + key := ek[pos : pos+keyLen] + pos += keyLen + + if ek[pos] != hashStartSep { + return nil, nil, errSetKey + } + + pos++ + member := ek[pos:] + return key, member, nil +} + +func (db *DB) sEncodeStartKey(key []byte) []byte { + return db.sEncodeSetKey(key, nil) +} + +func (db *DB) sEncodeStopKey(key []byte) []byte { + k := db.sEncodeSetKey(key, nil) + + k[len(k)-1] = setStopSep + + return k +} + +func (db *DB) sFlush() (drop int64, err error) { + minKey := make([]byte, 2) + minKey[0] = db.index + minKey[1] = SetType + + maxKey := make([]byte, 2) + maxKey[0] = db.index + maxKey[1] = SSizeType + 1 + + t := db.setTx + t.Lock() + defer t.Unlock() + + drop, err = db.flushRegion(t, minKey, maxKey) + err = db.expFlush(t, SetType) + + err = t.Commit() + return +} + +func (db *DB) sDelete(t *tx, key []byte) int64 { + sk := db.sEncodeSizeKey(key) + start := db.sEncodeStartKey(key) + stop := db.sEncodeStopKey(key) + + var num int64 = 0 + it := db.db.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1) + for ; it.Valid(); it.Next() { + t.Delete(it.RawKey()) + num++ + } + + it.Close() + + t.Delete(sk) + return num +} + +func (db *DB) sIncrSize(key []byte, delta int64) (int64, error) { + t := db.setTx + sk := db.sEncodeSizeKey(key) + + var err error + var size int64 = 0 + if size, err = Int64(db.db.Get(sk)); err != nil { + return 0, err + } else { + size += delta + if size <= 0 { + size = 0 + t.Delete(sk) + db.rmExpire(t, SetType, key) + } else { + t.Put(sk, PutInt64(size)) + } + } + + return size, nil +} + +func (db *DB) sExpireAt(key []byte, when int64) (int64, error) { + t := db.setTx + t.Lock() + defer t.Unlock() + + if scnt, err := db.SCard(key); err != nil || scnt == 0 { + return 0, err + } else { + db.expireAt(t, SetType, key, when) + if err := t.Commit(); err != nil { + return 0, err + } + + } + + return 1, nil +} + +func (db *DB) sSetItem(key []byte, member []byte) (int64, error) { + t := db.setTx + ek := db.sEncodeSetKey(key, member) + + var n int64 = 1 + if v, _ := db.db.Get(ek); v != nil { + n = 0 + } else { + if _, err := db.sIncrSize(key, 1); err != nil { + return 0, err + } + } + + t.Put(ek, nil) + return n, nil +} + +func (db *DB) SAdd(key []byte, args ...[]byte) (int64, error) { + t := db.setTx + t.Lock() + defer t.Unlock() + + var err error + var ek []byte + var num int64 = 0 + for i := 0; i < len(args); i++ { + if err := checkSetKMSize(key, args[i]); err != nil { + return 0, err + } + + ek = db.sEncodeSetKey(key, args[i]) + + if v, err := db.db.Get(ek); err != nil { + return 0, err + } else if v == nil { + num++ + } + + t.Put(ek, nil) + } + + if _, err = db.sIncrSize(key, num); err != nil { + return 0, err + } + + err = t.Commit() + return num, err + +} + +func (db *DB) SCard(key []byte) (int64, error) { + if err := checkKeySize(key); err != nil { + return 0, err + } + + sk := db.sEncodeSizeKey(key) + + return Int64(db.db.Get(sk)) +} + +func (db *DB) sDiffGeneric(keys ...[]byte) ([][]byte, error) { + destMap := make(map[string]bool) + + members, err := db.SMembers(keys[0]) + if err != nil { + return nil, err + } + + for _, m := range members { + destMap[String(m)] = true + } + + for _, k := range keys[1:] { + members, err := db.SMembers(k) + if err != nil { + return nil, err + } + + for _, m := range members { + if _, ok := destMap[String(m)]; !ok { + continue + } else if ok { + delete(destMap, String(m)) + } + } + // O - A = O, O is zero set. + if len(destMap) == 0 { + return nil, nil + } + } + + slice := make([][]byte, len(destMap)) + idx := 0 + for k, v := range destMap { + if !v { + continue + } + slice[idx] = []byte(k) + idx++ + } + + return slice, nil +} + +func (db *DB) SDiff(keys ...[]byte) ([][]byte, error) { + v, err := db.sDiffGeneric(keys...) + return v, err +} + +func (db *DB) SDiffStore(dstKey []byte, keys ...[]byte) (int64, error) { + n, err := db.sStoreGeneric(dstKey, DiffType, keys...) + return n, err +} + +func (db *DB) sInterGeneric(keys ...[]byte) ([][]byte, error) { + destMap := make(map[string]bool) + + members, err := db.SMembers(keys[0]) + if err != nil { + return nil, err + } + + for _, m := range members { + destMap[String(m)] = true + } + + for _, key := range keys[1:] { + if err := checkKeySize(key); err != nil { + return nil, err + } + + members, err := db.SMembers(key) + if err != nil { + return nil, err + } else if len(members) == 0 { + return nil, err + } + + tempMap := make(map[string]bool) + for _, member := range members { + if err := checkKeySize(member); err != nil { + return nil, err + } + if _, ok := destMap[String(member)]; ok { + tempMap[String(member)] = true //mark this item as selected + } + } + destMap = tempMap //reduce the size of the result set + if len(destMap) == 0 { + return nil, nil + } + } + + slice := make([][]byte, len(destMap)) + idx := 0 + for k, v := range destMap { + if !v { + continue + } + + slice[idx] = []byte(k) + idx++ + } + + return slice, nil + +} + +func (db *DB) SInter(keys ...[]byte) ([][]byte, error) { + v, err := db.sInterGeneric(keys...) + return v, err + +} + +func (db *DB) SInterStore(dstKey []byte, keys ...[]byte) (int64, error) { + n, err := db.sStoreGeneric(dstKey, InterType, keys...) + return n, err +} + +func (db *DB) SIsMember(key []byte, member []byte) (int64, error) { + ek := db.sEncodeSetKey(key, member) + + var n int64 = 1 + if v, err := db.db.Get(ek); err != nil { + return 0, err + } else if v == nil { + n = 0 + } + return n, nil +} + +func (db *DB) SMembers(key []byte) ([][]byte, error) { + if err := checkKeySize(key); err != nil { + return nil, err + } + + start := db.sEncodeStartKey(key) + stop := db.sEncodeStopKey(key) + + v := make([][]byte, 0, 16) + + it := db.db.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1) + for ; it.Valid(); it.Next() { + _, m, err := db.sDecodeSetKey(it.Key()) + if err != nil { + return nil, err + } + + v = append(v, m) + } + + it.Close() + + return v, nil +} + +func (db *DB) SRem(key []byte, args ...[]byte) (int64, error) { + t := db.setTx + t.Lock() + defer t.Unlock() + + var ek []byte + var v []byte + var err error + + it := db.db.NewIterator() + defer it.Close() + + var num int64 = 0 + for i := 0; i < len(args); i++ { + if err := checkSetKMSize(key, args[i]); err != nil { + return 0, err + } + + ek = db.sEncodeSetKey(key, args[i]) + + v = it.RawFind(ek) + if v == nil { + continue + } else { + num++ + t.Delete(ek) + } + } + + if _, err = db.sIncrSize(key, -num); err != nil { + return 0, err + } + + err = t.Commit() + return num, err + +} + +func (db *DB) sUnionGeneric(keys ...[]byte) ([][]byte, error) { + dstMap := make(map[string]bool) + + for _, key := range keys { + if err := checkKeySize(key); err != nil { + return nil, err + } + + members, err := db.SMembers(key) + if err != nil { + return nil, err + } + + for _, member := range members { + dstMap[String(member)] = true + } + } + + slice := make([][]byte, len(dstMap)) + idx := 0 + for k, v := range dstMap { + if !v { + continue + } + slice[idx] = []byte(k) + idx++ + } + + return slice, nil +} + +func (db *DB) SUnion(keys ...[]byte) ([][]byte, error) { + v, err := db.sUnionGeneric(keys...) + return v, err +} + +func (db *DB) SUnionStore(dstKey []byte, keys ...[]byte) (int64, error) { + n, err := db.sStoreGeneric(dstKey, UnionType, keys...) + return n, err +} + +func (db *DB) sStoreGeneric(dstKey []byte, optType byte, keys ...[]byte) (int64, error) { + if err := checkKeySize(dstKey); err != nil { + return 0, err + } + + t := db.setTx + t.Lock() + defer t.Unlock() + + db.sDelete(t, dstKey) + + var err error + var ek []byte + var num int64 = 0 + var v [][]byte + + switch optType { + case UnionType: + v, err = db.sUnionGeneric(keys...) + case DiffType: + v, err = db.sDiffGeneric(keys...) + case InterType: + v, err = db.sInterGeneric(keys...) + } + + if err != nil { + return 0, err + } + + for _, m := range v { + if err := checkSetKMSize(dstKey, m); err != nil { + return 0, err + } + + ek = db.sEncodeSetKey(dstKey, m) + + if v, err := db.db.Get(ek); err != nil { + return 0, err + } else if v == nil { + num++ + } + + t.Put(ek, nil) + + } + + if _, err = db.sIncrSize(dstKey, num); err != nil { + return 0, err + } + + err = t.Commit() + return num, err +} + +func (db *DB) SClear(key []byte) (int64, error) { + if err := checkKeySize(key); err != nil { + return 0, err + } + + t := db.setTx + t.Lock() + defer t.Unlock() + + num := db.sDelete(t, key) + db.rmExpire(t, SetType, key) + + err := t.Commit() + return num, err +} + +func (db *DB) SMclear(keys ...[]byte) (int64, error) { + t := db.setTx + t.Lock() + defer t.Unlock() + + for _, key := range keys { + if err := checkKeySize(key); err != nil { + return 0, err + } + + db.sDelete(t, key) + db.rmExpire(t, SetType, key) + } + + err := t.Commit() + return int64(len(keys)), err +} + +func (db *DB) SExpire(key []byte, duration int64) (int64, error) { + if duration <= 0 { + return 0, errExpireValue + } + + return db.sExpireAt(key, time.Now().Unix()+duration) + +} + +func (db *DB) SExpireAt(key []byte, when int64) (int64, error) { + if when <= time.Now().Unix() { + return 0, errExpireValue + } + + return db.sExpireAt(key, when) + +} + +func (db *DB) STTL(key []byte) (int64, error) { + if err := checkKeySize(key); err != nil { + return -1, err + } + + return db.ttl(SetType, key) +} + +func (db *DB) SPersist(key []byte) (int64, error) { + if err := checkKeySize(key); err != nil { + return 0, err + } + + t := db.setTx + t.Lock() + defer t.Unlock() + + n, err := db.rmExpire(t, SetType, key) + if err != nil { + return 0, err + } + err = t.Commit() + return n, err +} diff --git a/ledis/t_set_test.go b/ledis/t_set_test.go new file mode 100644 index 0000000..c5f6523 --- /dev/null +++ b/ledis/t_set_test.go @@ -0,0 +1,339 @@ +package ledis + +import ( + "testing" + "time" +) + +func TestSetCodec(t *testing.T) { + db := getTestDB() + + key := []byte("key") + member := []byte("member") + + ek := db.sEncodeSizeKey(key) + if k, err := db.sDecodeSizeKey(ek); err != nil { + t.Fatal(err) + } else if string(k) != "key" { + t.Fatal(string(k)) + } + + ek = db.sEncodeSetKey(key, member) + if k, m, err := db.sDecodeSetKey(ek); err != nil { + t.Fatal(err) + } else if string(k) != "key" { + t.Fatal(string(k)) + } else if string(m) != "member" { + t.Fatal(string(m)) + } +} + +func TestDBSet(t *testing.T) { + db := getTestDB() + + key := []byte("testdb_set_a") + member := []byte("member") + key1 := []byte("testdb_set_a1") + key2 := []byte("testdb_set_a2") + member1 := []byte("testdb_set_m1") + member2 := []byte("testdb_set_m2") + + // if n, err := db.sSetItem(key, []byte("m1")); err != nil { + // t.Fatal(err) + // } else if n != 1 { + // t.Fatal(n) + // } + + // if size, err := db.sIncrSize(key, 1); err != nil { + // t.Fatal(err) + // } else if size != 1 { + // t.Fatal(size) + // } + + if n, err := db.SAdd(key, member); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if cnt, err := db.SCard(key); err != nil { + t.Fatal(err) + } else if cnt != 1 { + t.Fatal(cnt) + } + + if n, err := db.SIsMember(key, member); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if v, err := db.SMembers(key); err != nil { + t.Fatal(err) + } else if string(v[0]) != "member" { + t.Fatal(string(v[0])) + } + + if n, err := db.SRem(key, member); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + db.SAdd(key1, member1, member2) + + if n, err := db.SClear(key1); err != nil { + t.Fatal(err) + } else if n != 2 { + t.Fatal(n) + } + + db.SAdd(key1, member1, member2) + db.SAdd(key2, member1, member2, []byte("xxx")) + + if n, _ := db.SCard(key2); n != 3 { + t.Fatal(n) + } + if n, err := db.SMclear(key1, key2); err != nil { + t.Fatal(err) + } else if n != 2 { + t.Fatal(n) + } + + db.SAdd(key2, member1, member2) + if n, err := db.SExpire(key2, 3600); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := db.SExpireAt(key2, time.Now().Unix()+3600); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := db.STTL(key2); err != nil { + t.Fatal(err) + } else if n < 0 { + t.Fatal(n) + } + + if n, err := db.SPersist(key2); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + +} + +func TestSetOperation(t *testing.T) { + db := getTestDB() + testUnion(db, t) + testInter(db, t) + testDiff(db, t) + +} + +func testUnion(db *DB, t *testing.T) { + + key := []byte("testdb_set_union_1") + key1 := []byte("testdb_set_union_2") + key2 := []byte("testdb_set_union_2") + // member1 := []byte("testdb_set_m1") + // member2 := []byte("testdb_set_m2") + + m1 := []byte("m1") + m2 := []byte("m2") + m3 := []byte("m3") + db.SAdd(key, m1, m2) + db.SAdd(key1, m1, m3) + db.SAdd(key2, m2, m3) + if _, err := db.sUnionGeneric(key, key2); err != nil { + t.Fatal(err) + } + + if _, err := db.SUnion(key, key2); err != nil { + t.Fatal(err) + } + + dstkey := []byte("union_dsk") + if num, err := db.SUnionStore(dstkey, key1, key2); err != nil { + t.Fatal(err) + } else if num != 3 { + t.Fatal(num) + } + if _, err := db.SMembers(dstkey); err != nil { + t.Fatal(err) + } + + if n, err := db.SCard(dstkey); err != nil { + t.Fatal(err) + } else if n != 3 { + t.Fatal(n) + } + + v1, _ := db.SUnion(key1, key2) + v2, _ := db.SUnion(key2, key1) + if len(v1) != len(v2) { + t.Fatal(v1, v2) + } + + v1, _ = db.SUnion(key, key1, key2) + v2, _ = db.SUnion(key, key2, key1) + if len(v1) != len(v2) { + t.Fatal(v1, v2) + } + + if v, err := db.SUnion(key, key); err != nil { + t.Fatal(err) + } else if len(v) != 2 { + t.Fatal(v) + } + + empKey := []byte("0") + if v, err := db.SUnion(key, empKey); err != nil { + t.Fatal(err) + } else if len(v) != 2 { + t.Fatal(v) + } +} + +func testInter(db *DB, t *testing.T) { + key1 := []byte("testdb_set_inter_1") + key2 := []byte("testdb_set_inter_2") + key3 := []byte("testdb_set_inter_3") + + m1 := []byte("m1") + m2 := []byte("m2") + m3 := []byte("m3") + m4 := []byte("m4") + + db.SAdd(key1, m1, m2) + db.SAdd(key2, m2, m3, m4) + db.SAdd(key3, m2, m4) + + if v, err := db.sInterGeneric(key1, key2); err != nil { + t.Fatal(err) + } else if len(v) != 1 { + t.Fatal(v) + } + + if v, err := db.SInter(key1, key2); err != nil { + t.Fatal(err) + } else if len(v) != 1 { + t.Fatal(v) + } + + dstKey := []byte("inter_dsk") + if n, err := db.SInterStore(dstKey, key1, key2); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + k1 := []byte("set_k1") + k2 := []byte("set_k2") + + db.SAdd(k1, m1, m3, m4) + db.SAdd(k2, m2, m3) + if n, err := db.SInterStore([]byte("set_xxx"), k1, k2); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + v1, _ := db.SInter(key1, key2) + v2, _ := db.SInter(key2, key1) + if len(v1) != len(v2) { + t.Fatal(v1, v2) + } + + v1, _ = db.SInter(key1, key2, key3) + v2, _ = db.SInter(key2, key3, key1) + if len(v1) != len(v2) { + t.Fatal(v1, v2) + } + + if v, err := db.SInter(key1, key1); err != nil { + t.Fatal(err) + } else if len(v) != 2 { + t.Fatal(v) + } + + empKey := []byte("0") + if v, err := db.SInter(key1, empKey); err != nil { + t.Fatal(err) + } else if len(v) != 0 { + t.Fatal(v) + } + + if v, err := db.SInter(empKey, key2); err != nil { + t.Fatal(err) + } else if len(v) != 0 { + t.Fatal(v) + } +} + +func testDiff(db *DB, t *testing.T) { + key0 := []byte("testdb_set_diff_0") + key1 := []byte("testdb_set_diff_1") + key2 := []byte("testdb_set_diff_2") + key3 := []byte("testdb_set_diff_3") + + m1 := []byte("m1") + m2 := []byte("m2") + m3 := []byte("m3") + m4 := []byte("m4") + + db.SAdd(key1, m1, m2) + db.SAdd(key2, m2, m3, m4) + db.SAdd(key3, m3) + + if _, err := db.sDiffGeneric(key1, key2); err != nil { + t.Fatal(err) + } + + if v, err := db.SDiff(key1, key2); err != nil { + t.Fatal(err) + } else if len(v) != 1 { + t.Fatal(v) + } + + dstKey := []byte("diff_dsk") + if n, err := db.SDiffStore(dstKey, key1, key2); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if v, err := db.SDiff(key2, key1); err != nil { + t.Fatal(err) + } else if len(v) != 2 { + t.Fatal(v) + } + + if v, err := db.SDiff(key1, key2, key3); err != nil { + t.Fatal(err) + } else if len(v) != 1 { + t.Fatal(v) //return 1 + } + + if v, err := db.SDiff(key2, key2); err != nil { + t.Fatal(err) + } else if len(v) != 0 { + t.Fatal(v) + } + + if v, err := db.SDiff(key0, key1); err != nil { + t.Fatal(err) + } else if len(v) != 0 { + t.Fatal(v) + } + + if v, err := db.SDiff(key1, key0); err != nil { + t.Fatal(err) + } else if len(v) != 2 { + t.Fatal(v) + } +} diff --git a/server/cmd_bit_test.go b/server/cmd_bit_test.go index 7e45d4e..f19850b 100644 --- a/server/cmd_bit_test.go +++ b/server/cmd_bit_test.go @@ -344,22 +344,6 @@ func TestBitErrorParams(t *testing.T) { t.Fatal("invalid err of %v", err) } - if _, err := c.Do("bexpire", "test_bexpire"); err == nil { - t.Fatal("invalid err of %v", err) - } - - if _, err := c.Do("bexpireat", "test_bexpireat"); err == nil { - t.Fatal("invalid err of %v", err) - } - - if _, err := c.Do("bttl"); err == nil { - t.Fatal("invalid err of %v", err) - } - - if _, err := c.Do("bpersist"); err == nil { - t.Fatal("invalid err of %v", err) - } - //bexpire if _, err := c.Do("bexpire", "test_bexpire"); err == nil { t.Fatal("invalid err of %v", err) diff --git a/server/cmd_set.go b/server/cmd_set.go new file mode 100644 index 0000000..815c3a8 --- /dev/null +++ b/server/cmd_set.go @@ -0,0 +1,283 @@ +package server + +import ( + "github.com/siddontang/ledisdb/ledis" +) + +func saddCommand(req *requestContext) error { + args := req.args + if len(args) < 2 { + return ErrCmdParams + } + + if n, err := req.db.SAdd(args[0], args[1:]...); err != nil { + return err + } else { + req.resp.writeInteger(n) + } + + return nil +} + +func soptGeneric(req *requestContext, optType byte) error { + args := req.args + if len(args) < 1 { + return ErrCmdParams + } + + var v [][]byte + var err error + + switch optType { + case ledis.UnionType: + v, err = req.db.SUnion(args...) + case ledis.DiffType: + v, err = req.db.SDiff(args...) + case ledis.InterType: + v, err = req.db.SInter(args...) + } + + if err != nil { + return err + } else { + req.resp.writeSliceArray(v) + } + + return nil + +} + +func soptStoreGeneric(req *requestContext, optType byte) error { + args := req.args + if len(args) < 2 { + return ErrCmdParams + } + + var n int64 + var err error + + switch optType { + case ledis.UnionType: + n, err = req.db.SUnionStore(args[0], args[1:]...) + case ledis.DiffType: + n, err = req.db.SDiffStore(args[0], args[1:]...) + case ledis.InterType: + n, err = req.db.SInterStore(args[0], args[1:]...) + } + + if err != nil { + return err + } else { + req.resp.writeInteger(n) + } + + return nil +} + +func scardCommand(req *requestContext) error { + args := req.args + if len(args) != 1 { + return ErrCmdParams + } + + if n, err := req.db.SCard(args[0]); err != nil { + return err + } else { + req.resp.writeInteger(n) + } + + return nil +} + +func sdiffCommand(req *requestContext) error { + return soptGeneric(req, ledis.DiffType) +} + +func sdiffstoreCommand(req *requestContext) error { + return soptStoreGeneric(req, ledis.DiffType) +} + +func sinterCommand(req *requestContext) error { + return soptGeneric(req, ledis.InterType) + +} + +func sinterstoreCommand(req *requestContext) error { + return soptStoreGeneric(req, ledis.InterType) +} + +func sismemberCommand(req *requestContext) error { + args := req.args + if len(args) != 2 { + return ErrCmdParams + } + + if n, err := req.db.SIsMember(args[0], args[1]); err != nil { + return err + } else { + req.resp.writeInteger(n) + } + + return nil +} + +func smembersCommand(req *requestContext) error { + args := req.args + if len(args) != 1 { + return ErrCmdParams + } + + if v, err := req.db.SMembers(args[0]); err != nil { + return err + } else { + req.resp.writeSliceArray(v) + } + + return nil + +} + +func sremCommand(req *requestContext) error { + args := req.args + if len(args) < 2 { + return ErrCmdParams + } + + if n, err := req.db.SRem(args[0], args[1:]...); err != nil { + return err + } else { + req.resp.writeInteger(n) + } + + return nil + +} + +func sunionCommand(req *requestContext) error { + return soptGeneric(req, ledis.UnionType) +} + +func sunionstoreCommand(req *requestContext) error { + return soptStoreGeneric(req, ledis.UnionType) +} + +func sclearCommand(req *requestContext) error { + args := req.args + if len(args) != 1 { + return ErrCmdParams + } + + if n, err := req.db.SClear(args[0]); err != nil { + return err + } else { + req.resp.writeInteger(n) + } + + return nil +} + +func smclearCommand(req *requestContext) error { + args := req.args + if len(args) < 1 { + return ErrCmdParams + } + + if n, err := req.db.SMclear(args...); err != nil { + return err + } else { + req.resp.writeInteger(n) + } + + return nil +} + +func sexpireCommand(req *requestContext) error { + args := req.args + if len(args) != 2 { + return ErrCmdParams + } + + duration, err := ledis.StrInt64(args[1], nil) + if err != nil { + return ErrValue + } + + if v, err := req.db.SExpire(args[0], duration); err != nil { + return err + } else { + req.resp.writeInteger(v) + } + + return nil +} + +func sexpireAtCommand(req *requestContext) error { + args := req.args + if len(args) != 2 { + return ErrCmdParams + } + + when, err := ledis.StrInt64(args[1], nil) + if err != nil { + return ErrValue + } + + if v, err := req.db.SExpireAt(args[0], when); err != nil { + return err + } else { + req.resp.writeInteger(v) + } + + return nil +} + +func sttlCommand(req *requestContext) error { + args := req.args + if len(args) != 1 { + return ErrCmdParams + } + + if v, err := req.db.STTL(args[0]); err != nil { + return err + } else { + req.resp.writeInteger(v) + } + + return nil + +} + +func spersistCommand(req *requestContext) error { + args := req.args + if len(args) != 1 { + return ErrCmdParams + } + + if n, err := req.db.SPersist(args[0]); err != nil { + return err + } else { + req.resp.writeInteger(n) + } + + return nil +} + +func init() { + register("sadd", saddCommand) + register("scard", scardCommand) + register("sdiff", sdiffCommand) + register("sdiffstore", sdiffstoreCommand) + register("sinter", sinterCommand) + register("sinterstore", sinterstoreCommand) + register("sismember", sismemberCommand) + register("smembers", smembersCommand) + register("srem", sremCommand) + register("sunion", sunionCommand) + register("sunionstore", sunionstoreCommand) + register("sclear", sclearCommand) + register("smclear", smclearCommand) + register("sexpire", sexpireCommand) + register("sexpireat", sexpireAtCommand) + register("sttl", sttlCommand) + register("spersist", spersistCommand) +} diff --git a/server/cmd_set_test.go b/server/cmd_set_test.go new file mode 100644 index 0000000..0e76520 --- /dev/null +++ b/server/cmd_set_test.go @@ -0,0 +1,203 @@ +package server + +import ( + "github.com/siddontang/ledisdb/client/go/ledis" + "testing" +) + +func TestSet(t *testing.T) { + c := getTestConn() + defer c.Close() + + key1 := "testdb_cmd_set_1" + key2 := "testdb_cmd_set_2" + + if n, err := ledis.Int(c.Do("sadd", key1, 0, 1)); err != nil { + t.Fatal(err) + } else if n != 2 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("sadd", key2, 0, 1, 2, 3)); err != nil { + t.Fatal(err) + } else if n != 4 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("scard", key1)); err != nil { + t.Fatal(err) + } else if n != 2 { + t.Fatal(n) + } + + if n, err := ledis.MultiBulk(c.Do("sdiff", key2, key1)); err != nil { + t.Fatal(err) + } else if len(n) != 2 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("sdiffstore", []byte("cmd_set_em1"), key2, key1)); err != nil { + t.Fatal(err) + } else if n != 2 { + t.Fatal(n) + } + + if n, err := ledis.MultiBulk(c.Do("sunion", key1, key2)); err != nil { + t.Fatal(err) + } else if len(n) != 4 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("sunionstore", []byte("cmd_set_em2"), key1, key2)); err != nil { + t.Fatal(err) + } else if n != 4 { + t.Fatal(n) + } + + if n, err := ledis.MultiBulk(c.Do("sinter", key1, key2)); err != nil { + t.Fatal(err) + } else if len(n) != 2 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("sinterstore", []byte("cmd_set_em3"), key1, key2)); err != nil { + t.Fatal(err) + } else if n != 2 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("srem", key1, 0, 1)); err != nil { + t.Fatal(err) + } else if n != 2 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("sismember", key2, 0)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.MultiBulk(c.Do("smembers", key2)); err != nil { + t.Fatal(err) + } else if len(n) != 4 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("sclear", key2)); err != nil { + t.Fatal(err) + } else if n != 4 { + t.Fatal(n) + } + + c.Do("sadd", key1, 0) + c.Do("sadd", key2, 1) + if n, err := ledis.Int(c.Do("smclear", key1, key2)); err != nil { + t.Fatal(err) + } else if n != 2 { + t.Fatal(n) + } + +} + +func TestSetErrorParams(t *testing.T) { + c := getTestConn() + defer c.Close() + + if _, err := c.Do("sadd", "test_sadd"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("scard"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("scard", "k1", "k2"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sdiff"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sdiffstore", "dstkey"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sinter"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sinterstore", "dstkey"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sunion"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sunionstore", "dstkey"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sismember", "k1"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sismember", "k1", "m1", "m2"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("smembers"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("smembers", "k1", "k2"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("srem"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("srem", "key"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sclear"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sclear", "k1", "k2"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("smclear"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sexpire", "set_expire"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sexpire", "set_expire", "aaa"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sexpireat", "set_expireat"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sexpireat", "set_expireat", "aaa"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("sttl"); err == nil { + t.Fatal("invalid err of %v", err) + } + + if _, err := c.Do("spersist"); err == nil { + t.Fatal("invalid err of %v", err) + } + +} diff --git a/server/cmd_ttl_test.go b/server/cmd_ttl_test.go index 664ca87..702348d 100644 --- a/server/cmd_ttl_test.go +++ b/server/cmd_ttl_test.go @@ -79,3 +79,353 @@ func TestKVExpire(t *testing.T) { } } + +func TestSetExpire(t *testing.T) { + c := getTestConn() + defer c.Close() + + k := "set_ttl" + c.Do("sadd", k, "123") + + // expire + ttl + exp := int64(10) + if n, err := ledis.Int(c.Do("sexpire", k, exp)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if ttl, err := ledis.Int64(c.Do("sttl", k)); err != nil { + t.Fatal(err) + } else if ttl != exp { + t.Fatal(ttl) + } + + // expireat + ttl + tm := now() + 3 + if n, err := ledis.Int(c.Do("sexpireat", k, tm)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if ttl, err := ledis.Int64(c.Do("sttl", k)); err != nil { + t.Fatal(err) + } else if ttl != 3 { + t.Fatal(ttl) + } + + kErr := "not_exist_ttl" + + // err - expire, expireat + if n, err := ledis.Int(c.Do("sexpire", kErr, tm)); err != nil || n != 0 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("sexpireat", kErr, tm)); err != nil || n != 0 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("sttl", kErr)); err != nil || n != -1 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("spersist", k)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("sexpire", k, 10)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("spersist", k)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + +} + +func TestListExpire(t *testing.T) { + c := getTestConn() + defer c.Close() + + k := "list_ttl" + c.Do("rpush", k, "123") + + // expire + ttl + exp := int64(10) + if n, err := ledis.Int(c.Do("lexpire", k, exp)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if ttl, err := ledis.Int64(c.Do("lttl", k)); err != nil { + t.Fatal(err) + } else if ttl != exp { + t.Fatal(ttl) + } + + // expireat + ttl + tm := now() + 3 + if n, err := ledis.Int(c.Do("lexpireat", k, tm)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if ttl, err := ledis.Int64(c.Do("lttl", k)); err != nil { + t.Fatal(err) + } else if ttl != 3 { + t.Fatal(ttl) + } + + kErr := "not_exist_ttl" + + // err - expire, expireat + if n, err := ledis.Int(c.Do("lexpire", kErr, tm)); err != nil || n != 0 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("lexpireat", kErr, tm)); err != nil || n != 0 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("lttl", kErr)); err != nil || n != -1 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("lpersist", k)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("lexpire", k, 10)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("lpersist", k)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + +} + +func TestHashExpire(t *testing.T) { + c := getTestConn() + defer c.Close() + + k := "hash_ttl" + c.Do("hset", k, "f", 123) + + // expire + ttl + exp := int64(10) + if n, err := ledis.Int(c.Do("hexpire", k, exp)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if ttl, err := ledis.Int64(c.Do("httl", k)); err != nil { + t.Fatal(err) + } else if ttl != exp { + t.Fatal(ttl) + } + + // expireat + ttl + tm := now() + 3 + if n, err := ledis.Int(c.Do("hexpireat", k, tm)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if ttl, err := ledis.Int64(c.Do("httl", k)); err != nil { + t.Fatal(err) + } else if ttl != 3 { + t.Fatal(ttl) + } + + kErr := "not_exist_ttl" + + // err - expire, expireat + if n, err := ledis.Int(c.Do("hexpire", kErr, tm)); err != nil || n != 0 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("hexpireat", kErr, tm)); err != nil || n != 0 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("httl", kErr)); err != nil || n != -1 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("hpersist", k)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("hexpire", k, 10)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("hpersist", k)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + +} + +func TestZsetExpire(t *testing.T) { + c := getTestConn() + defer c.Close() + + k := "zset_ttl" + c.Do("zadd", k, 123, "m") + + // expire + ttl + exp := int64(10) + if n, err := ledis.Int(c.Do("zexpire", k, exp)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if ttl, err := ledis.Int64(c.Do("zttl", k)); err != nil { + t.Fatal(err) + } else if ttl != exp { + t.Fatal(ttl) + } + + // expireat + ttl + tm := now() + 3 + if n, err := ledis.Int(c.Do("zexpireat", k, tm)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if ttl, err := ledis.Int64(c.Do("zttl", k)); err != nil { + t.Fatal(err) + } else if ttl != 3 { + t.Fatal(ttl) + } + + kErr := "not_exist_ttl" + + // err - expire, expireat + if n, err := ledis.Int(c.Do("zexpire", kErr, tm)); err != nil || n != 0 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("zexpireat", kErr, tm)); err != nil || n != 0 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("zttl", kErr)); err != nil || n != -1 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("zpersist", k)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("zexpire", k, 10)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("zpersist", k)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + +} + +func TestBitmapExpire(t *testing.T) { + c := getTestConn() + defer c.Close() + + k := "bit_ttl" + c.Do("bsetbit", k, 0, 1) + + // expire + ttl + exp := int64(10) + if n, err := ledis.Int(c.Do("bexpire", k, exp)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if ttl, err := ledis.Int64(c.Do("bttl", k)); err != nil { + t.Fatal(err) + } else if ttl != exp { + t.Fatal(ttl) + } + + // expireat + ttl + tm := now() + 3 + if n, err := ledis.Int(c.Do("bexpireat", k, tm)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if ttl, err := ledis.Int64(c.Do("bttl", k)); err != nil { + t.Fatal(err) + } else if ttl != 3 { + t.Fatal(ttl) + } + + kErr := "not_exist_ttl" + + // err - expire, expireat + if n, err := ledis.Int(c.Do("bexpire", kErr, tm)); err != nil || n != 0 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("bexpireat", kErr, tm)); err != nil || n != 0 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("bttl", kErr)); err != nil || n != -1 { + t.Fatal(false) + } + + if n, err := ledis.Int(c.Do("bpersist", k)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("bexpire", k, 10)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + if n, err := ledis.Int(c.Do("bpersist", k)); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + +}