Merge branch 'develop'

This commit is contained in:
siddontang 2014-08-17 14:59:32 +08:00
commit a50c84ddc4
67 changed files with 3729 additions and 858 deletions

View File

@ -1,12 +1,12 @@
# LedisDB # 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 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. LedisDB now supports multiple databases as backend to store data, you can test and choose the proper one for you.
## Features ## Features
+ Rich advanced data structure: KV, List, Hash, ZSet, Bitmap. + Rich data structure: KV, List, Hash, ZSet, Bitmap, Set.
+ Stores lots of data, over the memory limit. + Stores lots of data, over the memory limit.
+ Various backend database to use: LevelDB, goleveldb, LMDB, RocksDB, BoltDB, HyperLevelDB. + Various backend database to use: LevelDB, goleveldb, LMDB, RocksDB, BoltDB, HyperLevelDB.
+ Supports expiration and ttl. + Supports expiration and ttl.

View File

@ -2,14 +2,12 @@ from __future__ import with_statement
import datetime import datetime
import time as mod_time import time as mod_time
from ledis._compat import (b, izip, imap, iteritems, from ledis._compat import (b, izip, imap, iteritems,
basestring, long, nativestr, urlparse, bytes) basestring, long, nativestr, bytes)
from ledis.connection import ConnectionPool, UnixDomainSocketConnection from ledis.connection import ConnectionPool, UnixDomainSocketConnection
from ledis.exceptions import ( from ledis.exceptions import (
ConnectionError, ConnectionError,
DataError, DataError,
LedisError, LedisError,
ResponseError,
ExecAbortError,
) )
SYM_EMPTY = b('') SYM_EMPTY = b('')
@ -75,15 +73,18 @@ class Ledis(object):
""" """
RESPONSE_CALLBACKS = dict_merge( RESPONSE_CALLBACKS = dict_merge(
string_keys_to_dict( string_keys_to_dict(
'EXISTS EXPIRE EXPIREAT HEXISTS HMSET SETNX ' 'EXISTS HEXISTS SISMEMBER HMSET SETNX'
'PERSIST HPERSIST LPERSIST ZPERSIST BEXPIRE ' 'PERSIST HPERSIST LPERSIST ZPERSIST SPERSIST BPERSIST'
'BEXPIREAT BPERSIST BDELETE', 'EXPIRE LEXPIRE HEXPIRE SEXPIRE ZEXPIRE BEXPIRE'
'EXPIREAT LBEXPIREAT HEXPIREAT SEXPIREAT ZEXPIREAT BEXPIREAT',
bool bool
), ),
string_keys_to_dict( string_keys_to_dict(
'DECRBY DEL HDEL HLEN INCRBY LLEN ' 'DECRBY DEL HDEL HLEN INCRBY LLEN ZADD ZCARD ZREM'
'ZADD ZCARD ZREM ZREMRANGEBYRANK ZREMRANGEBYSCORE' 'ZREMRANGEBYRANK ZREMRANGEBYSCORE LMCLEAR HMCLEAR'
'LMCLEAR HMCLEAR ZMCLEAR BCOUNT BGETBIT BSETBIT BOPT BMSETBIT', 'ZMCLEAR BCOUNT BGETBIT BSETBIT BOPT BMSETBIT'
'SADD SCARD SDIFFSTORE SINTERSTORE SUNIONSTORE SREM'
'SCLEAR SMLEAR BDELETE',
int int
), ),
string_keys_to_dict( string_keys_to_dict(
@ -94,6 +95,10 @@ class Ledis(object):
'MSET SELECT ', 'MSET SELECT ',
lambda r: nativestr(r) == 'OK' lambda r: nativestr(r) == 'OK'
), ),
string_keys_to_dict(
'SDIFF SINTER SMEMBERS SUNION',
lambda r: r and set(r) or set()
),
string_keys_to_dict( string_keys_to_dict(
'ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE', 'ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE',
zset_score_pairs zset_score_pairs
@ -403,6 +408,103 @@ class Ledis(object):
return self.execute_command('LPERSIST', name) return self.execute_command('LPERSIST', name)
#### SET COMMANDS ####
def sadd(self, name, *values):
"Add ``value(s)`` to set ``name``"
return self.execute_command('SADD', name, *values)
def scard(self, name):
"Return the number of elements in set ``name``"
return self.execute_command('SCARD', name)
def sdiff(self, keys, *args):
"Return the difference of sets specified by ``keys``"
args = list_or_args(keys, args)
return self.execute_command('SDIFF', *args)
def sdiffstore(self, dest, keys, *args):
"""
Store the difference of sets specified by ``keys`` into a new
set named ``dest``. Returns the number of keys in the new set.
"""
args = list_or_args(keys, args)
return self.execute_command('SDIFFSTORE', dest, *args)
def sinter(self, keys, *args):
"Return the intersection of sets specified by ``keys``"
args = list_or_args(keys, args)
return self.execute_command('SINTER', *args)
def sinterstore(self, dest, keys, *args):
"""
Store the intersection of sets specified by ``keys`` into a new
set named ``dest``. Returns the number of keys in the new set.
"""
args = list_or_args(keys, args)
return self.execute_command('SINTERSTORE', dest, *args)
def sismember(self, name, value):
"Return a boolean indicating if ``value`` is a member of set ``name``"
return self.execute_command('SISMEMBER', name, value)
def smembers(self, name):
"Return all members of the set ``name``"
return self.execute_command('SMEMBERS', name)
def srem(self, name, *values):
"Remove ``values`` from set ``name``"
return self.execute_command('SREM', name, *values)
def sunion(self, keys, *args):
"Return the union of sets specified by ``keys``"
args = list_or_args(keys, args)
return self.execute_command('SUNION', *args)
def sunionstore(self, dest, keys, *args):
"""
Store the union of sets specified by ``keys`` into a new
set named ``dest``. Returns the number of keys in the new set.
"""
args = list_or_args(keys, args)
return self.execute_command('SUNIONSTORE', dest, *args)
# SPECIAL COMMANDS SUPPORTED BY LEDISDB
def sclear(self, name):
"Delete key ``name`` from set"
return self.execute_command('SCLEAR', name)
def smclear(self, *names):
"Delete multiple keys ``names`` from set"
return self.execute_command('SMCLEAR', *names)
def sexpire(self, name, time):
"""
Set an expire flag on key name for time milliseconds.
time can be represented by an integer or a Python timedelta object.
"""
if isinstance(time, datetime.timedelta):
time = time.seconds + time.days * 24 * 3600
return self.execute_command('SEXPIRE', name, time)
def sexpireat(self, name, when):
"""
Set an expire flag on key name. when can be represented as an integer
representing unix time in milliseconds (unix time * 1000) or a
Python datetime object.
"""
if isinstance(when, datetime.datetime):
when = int(mod_time.mktime(when.timetuple()))
return self.execute_command('SEXPIREAT', name, when)
def sttl(self, name):
"Returns the number of seconds until the key name will expire"
return self.execute_command('STTL', name)
def spersist(self, name):
"Removes an expiration on name"
return self.execute_command('SPERSIST', name)
#### SORTED SET COMMANDS #### #### SORTED SET COMMANDS ####
def zadd(self, name, *args, **kwargs): def zadd(self, name, *args, **kwargs):
""" """
@ -693,7 +795,6 @@ class Ledis(object):
return self.execute_command('HPERSIST', name) return self.execute_command('HPERSIST', name)
### BIT COMMANDS ### BIT COMMANDS
def bget(self, name): def bget(self, name):
"" ""

View File

@ -3,21 +3,15 @@
import unittest import unittest
import sys import sys
import datetime, time
sys.path.append('..') sys.path.append('..')
import ledis import ledis
from ledis._compat import b from ledis._compat import b
from ledis import ResponseError from util import expire_at, expire_at_seconds
l = ledis.Ledis(port=6380) l = ledis.Ledis(port=6380)
def current_time():
return datetime.datetime.now()
class TestCmdBit(unittest.TestCase): class TestCmdBit(unittest.TestCase):
def setUp(self): def setUp(self):
pass pass
@ -94,21 +88,17 @@ class TestCmdBit(unittest.TestCase):
assert l.bttl('a') == -1 assert l.bttl('a') == -1
def test_bexpireat_datetime(self): def test_bexpireat_datetime(self):
expire_at = current_time() + datetime.timedelta(minutes=1)
l.bsetbit('a', 1, True) l.bsetbit('a', 1, True)
assert l.bexpireat('a', expire_at) assert l.bexpireat('a', expire_at())
assert 0 < l.bttl('a') <= 61 assert 0 < l.bttl('a') <= 61
def test_bexpireat_unixtime(self): def test_bexpireat_unixtime(self):
expire_at = current_time() + datetime.timedelta(minutes=1)
l.bsetbit('a', 1, True) l.bsetbit('a', 1, True)
expire_at_seconds = int(time.mktime(expire_at.timetuple())) assert l.bexpireat('a', expire_at_seconds())
assert l.bexpireat('a', expire_at_seconds)
assert 0 < l.bttl('a') <= 61 assert 0 < l.bttl('a') <= 61
def test_bexpireat_no_key(self): def test_bexpireat_no_key(self):
expire_at = current_time() + datetime.timedelta(minutes=1) assert not l.bexpireat('a', expire_at())
assert not l.bexpireat('a', expire_at)
def test_bttl_and_bpersist(self): def test_bttl_and_bpersist(self):
l.bsetbit('a', 1, True) l.bsetbit('a', 1, True)

View File

@ -3,19 +3,16 @@
import unittest import unittest
import sys import sys
import datetime, time
sys.path.append('..') sys.path.append('..')
import ledis import ledis
from ledis._compat import b, iteritems, itervalues from ledis._compat import itervalues
from ledis import ResponseError from util import expire_at, expire_at_seconds
l = ledis.Ledis(port=6380) l = ledis.Ledis(port=6380)
def current_time():
return datetime.datetime.now()
class TestCmdHash(unittest.TestCase): class TestCmdHash(unittest.TestCase):
def setUp(self): def setUp(self):
@ -35,7 +32,7 @@ class TestCmdHash(unittest.TestCase):
l.hset('myhash', 'field1', 'foo') l.hset('myhash', 'field1', 'foo')
l.hdel('myhash', 'field2') l.hdel('myhash', 'field2')
assert l.hexists('myhash', 'field1') == 1 assert l.hexists('myhash', 'field1') == 1
assert l.hexists('myhash', 'field2') == 0 assert l.hexists('myhash', 'field2') == 0
def test_hget(self): def test_hget(self):
l.hset('myhash', 'field1', 'foo') l.hset('myhash', 'field1', 'foo')
@ -110,21 +107,17 @@ class TestCmdHash(unittest.TestCase):
assert l.httl('myhash') <= 100 assert l.httl('myhash') <= 100
def test_hexpireat_datetime(self): def test_hexpireat_datetime(self):
expire_at = current_time() + datetime.timedelta(minutes=1)
l.hset('a', 'f', 'foo') l.hset('a', 'f', 'foo')
assert l.hexpireat('a', expire_at) assert l.hexpireat('a', expire_at())
assert 0 < l.httl('a') <= 61 assert 0 < l.httl('a') <= 61
def test_hexpireat_unixtime(self): def test_hexpireat_unixtime(self):
expire_at = current_time() + datetime.timedelta(minutes=1)
l.hset('a', 'f', 'foo') l.hset('a', 'f', 'foo')
expire_at_seconds = int(time.mktime(expire_at.timetuple())) assert l.hexpireat('a', expire_at_seconds())
assert l.hexpireat('a', expire_at_seconds)
assert 0 < l.httl('a') <= 61 assert 0 < l.httl('a') <= 61
def test_hexpireat_no_key(self): def test_hexpireat_no_key(self):
expire_at = current_time() + datetime.timedelta(minutes=1) assert not l.hexpireat('a', expire_at())
assert not l.hexpireat('a', expire_at)
def test_hexpireat(self): def test_hexpireat(self):
assert l.hexpireat('myhash', 1577808000) == 0 assert l.hexpireat('myhash', 1577808000) == 0

View File

@ -3,19 +3,16 @@
import unittest import unittest
import sys import sys
import datetime, time
sys.path.append('..') sys.path.append('..')
import ledis import ledis
from ledis._compat import b, iteritems from ledis._compat import b, iteritems
from util import expire_at, expire_at_seconds
l = ledis.Ledis(port=6380) l = ledis.Ledis(port=6380)
def current_time():
return datetime.datetime.now()
class TestCmdKv(unittest.TestCase): class TestCmdKv(unittest.TestCase):
def setUp(self): def setUp(self):
pass pass
@ -32,10 +29,6 @@ class TestCmdKv(unittest.TestCase):
assert l.decr('a', amount=5) == -7 assert l.decr('a', amount=5) == -7
assert l['a'] == b('-7') assert l['a'] == b('-7')
#FIXME: how to test exception?
# l.set('b', '234293482390480948029348230948')
# self.assertRaises(ResponseError, l.delete('b'))
def test_decrby(self): def test_decrby(self):
assert l.delete('a') == 1 assert l.delete('a') == 1
assert l.decrby('a') == -1 assert l.decrby('a') == -1
@ -134,21 +127,17 @@ class TestCmdKv(unittest.TestCase):
assert not (l.expire('a', 100)) assert not (l.expire('a', 100))
def test_expireat_datetime(self): def test_expireat_datetime(self):
expire_at = current_time() + datetime.timedelta(minutes=1)
l.set('a', '1') l.set('a', '1')
assert l.expireat('a', expire_at) assert l.expireat('a', expire_at())
assert 0 < l.ttl('a') <= 61 assert 0 < l.ttl('a') <= 61
def test_expireat_unixtime(self): def test_expireat_unixtime(self):
expire_at = current_time() + datetime.timedelta(minutes=1)
l.set('a', '1') l.set('a', '1')
expire_at_seconds = int(time.mktime(expire_at.timetuple())) assert l.expireat('a', expire_at_seconds())
assert l.expireat('a', expire_at_seconds)
assert 0 < l.ttl('a') <= 61 assert 0 < l.ttl('a') <= 61
def test_expireat_no_key(self): def test_expireat_no_key(self):
expire_at = current_time() + datetime.timedelta(minutes=1) assert not l.expireat('a', expire_at())
assert not l.expireat('a', expire_at)
def test_expireat(self): def test_expireat(self):
l.set('a', 'hello') l.set('a', 'hello')

View File

@ -2,20 +2,17 @@
# Test Cases for list commands # Test Cases for list commands
import unittest import unittest
import datetime, time
import sys import sys
sys.path.append('..') sys.path.append('..')
import ledis import ledis
from ledis._compat import b from ledis._compat import b
from util import expire_at, expire_at_seconds
l = ledis.Ledis(port=6380) l = ledis.Ledis(port=6380)
def current_time():
return datetime.datetime.now()
class TestCmdList(unittest.TestCase): class TestCmdList(unittest.TestCase):
def setUp(self): def setUp(self):
pass pass
@ -84,21 +81,17 @@ class TestCmdList(unittest.TestCase):
assert l.lttl('mylist') == -1 assert l.lttl('mylist') == -1
def test_lexpireat_datetime(self): def test_lexpireat_datetime(self):
expire_at = current_time() + datetime.timedelta(minutes=1)
l.rpush('mylist', '1') l.rpush('mylist', '1')
assert l.lexpireat('mylist', expire_at) assert l.lexpireat('mylist', expire_at())
assert 0 < l.lttl('mylist') <= 61 assert 0 < l.lttl('mylist') <= 61
def test_lexpireat_unixtime(self): def test_lexpireat_unixtime(self):
expire_at = current_time() + datetime.timedelta(minutes=1)
l.rpush('mylist', '1') l.rpush('mylist', '1')
expire_at_seconds = int(time.mktime(expire_at.timetuple())) assert l.lexpireat('mylist', expire_at_seconds())
assert l.lexpireat('mylist', expire_at_seconds)
assert l.lttl('mylist') <= 61 assert l.lttl('mylist') <= 61
def test_lexpireat_no_key(self): def test_lexpireat_no_key(self):
expire_at = current_time() + datetime.timedelta(minutes=1) assert not l.lexpireat('mylist', expire_at())
assert not l.lexpireat('mylist', expire_at)
def test_lttl_and_lpersist(self): def test_lttl_and_lpersist(self):
l.rpush('mylist', '1') l.rpush('mylist', '1')

View File

@ -0,0 +1,156 @@
# coding: utf-8
# Test set commands
import unittest
import sys
sys.path.append('..')
import pytest
import ledis
from ledis._compat import b
from ledis import ResponseError
from util import expire_at, expire_at_seconds
l = ledis.Ledis(port=6380)
class TestCmdSet(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
l.smclear('a', 'b', 'c')
def test_sadd(self):
members = set([b('1'), b('2'), b('3')])
l.sadd('a', *members)
assert l.smembers('a') == members
def test_scard(self):
l.sadd('a', '1', '2', '3')
assert l.scard('a') == 3
def test_sdiff(self):
l.sadd('a', '1', '2', '3')
assert l.sdiff('a', 'b') == set([b('1'), b('2'), b('3')])
l.sadd('b', '2', '3')
assert l.sdiff('a', 'b') == set([b('1')])
def test_sdiffstore(self):
l.sadd('a', '1', '2', '3')
assert l.sdiffstore('c', 'a', 'b') == 3
assert l.smembers('c') == set([b('1'), b('2'), b('3')])
l.sadd('b', '2', '3')
print l.smembers('c')
print "before"
assert l.sdiffstore('c', 'a', 'b') == 1
print l.smembers('c')
assert l.smembers('c') == set([b('1')])
def test_sinter(self):
l.sadd('a', '1', '2', '3')
assert l.sinter('a', 'b') == set()
l.sadd('b', '2', '3')
assert l.sinter('a', 'b') == set([b('2'), b('3')])
def test_sinterstore(self):
l.sadd('a', '1', '2', '3')
assert l.sinterstore('c', 'a', 'b') == 0
assert l.smembers('c') == set()
l.sadd('b', '2', '3')
assert l.sinterstore('c', 'a', 'b') == 2
assert l.smembers('c') == set([b('2'), b('3')])
def test_sismember(self):
l.sadd('a', '1', '2', '3')
assert l.sismember('a', '1')
assert l.sismember('a', '2')
assert l.sismember('a', '3')
assert not l.sismember('a', '4')
def test_smembers(self):
l.sadd('a', '1', '2', '3')
assert l.smembers('a') == set([b('1'), b('2'), b('3')])
def test_srem(self):
l.sadd('a', '1', '2', '3', '4')
assert l.srem('a', '5') == 0
assert l.srem('a', '2', '4') == 2
assert l.smembers('a') == set([b('1'), b('3')])
def test_sunion(self):
l.sadd('a', '1', '2')
l.sadd('b', '2', '3')
assert l.sunion('a', 'b') == set([b('1'), b('2'), b('3')])
def test_sunionstore(self):
l.sadd('a', '1', '2')
l.sadd('b', '2', '3')
assert l.sunionstore('c', 'a', 'b') == 3
assert l.smembers('c') == set([b('1'), b('2'), b('3')])
def test_sclear(self):
members = set([b('1'), b('2'), b('3')])
l.sadd('a', *members)
assert l.sclear('a') == 3
assert l.sclear('a') == 0
def test_smclear(self):
members = set([b('1'), b('2'), b('3')])
l.sadd('a', *members)
l.sadd('b', *members)
assert l.smclear('a', 'b') == 2
def test_sexpire(self):
members = set([b('1'), b('2'), b('3')])
assert l.sexpire('a', 100) == 0
l.sadd('a', *members)
assert l.sexpire('a', 100) == 1
assert l.sttl('a') <= 100
def test_sexpireat_datetime(self):
members = set([b('1'), b('2'), b('3')])
l.sadd('a', *members)
assert l.sexpireat('a', expire_at())
assert 0 < l.sttl('a') <= 61
def test_sexpireat_unixtime(self):
members = set([b('1'), b('2'), b('3')])
l.sadd('a', *members)
assert l.sexpireat('a', expire_at_seconds())
assert 0 < l.sttl('a') <= 61
def test_sexpireat_no_key(self):
assert not l.sexpireat('a', expire_at())
def test_sexpireat(self):
assert l.sexpireat('a', 1577808000) == 0
members = set([b('1'), b('2'), b('3')])
l.sadd('a', *members)
assert l.sexpireat('a', 1577808000) == 1
def test_sttl(self):
members = set([b('1'), b('2'), b('3')])
l.sadd('a', *members)
assert l.sexpire('a', 100)
assert l.sttl('a') <= 100
def test_spersist(self):
members = set([b('1'), b('2'), b('3')])
l.sadd('a', *members)
l.sexpire('a', 100)
assert l.sttl('a') <= 100
assert l.spersist('a')
assert l.sttl('a') == -1
def test_invalid_params(self):
with pytest.raises(ResponseError) as excinfo:
l.sadd("a")
assert excinfo.value.message == "invalid command param"
def test_invalid_value(self):
members = set([b('1'), b('2'), b('3')])
l.sadd('a', *members)
self.assertRaises(ResponseError, lambda: l.sexpire('a', 'a'))

View File

@ -3,17 +3,14 @@
import unittest import unittest
import sys import sys
import datetime, time
sys.path.append('..') sys.path.append('..')
import ledis import ledis
from ledis._compat import b, iteritems from ledis._compat import b
from ledis import ResponseError from util import expire_at, expire_at_seconds
l = ledis.Ledis(port=6380) l = ledis.Ledis(port=6380)
def current_time():
return datetime.datetime.now()
class TestCmdZset(unittest.TestCase): class TestCmdZset(unittest.TestCase):
def setUp(self): def setUp(self):
@ -62,7 +59,7 @@ class TestCmdZset(unittest.TestCase):
assert l.zrangebyscore('a', 2, 4, start=1, num=2) == \ assert l.zrangebyscore('a', 2, 4, start=1, num=2) == \
[b('a3'), b('a4')] [b('a3'), b('a4')]
# withscores # withscores
assert l.zrangebyscore('a', 2, 4, withscores=True) == \ assert l.zrangebyscore('a', 2, 4, withscores=True) == \
[('a2', 2), ('a3', 3), ('a4', 4)] [('a2', 2), ('a3', 3), ('a4', 4)]
@ -145,21 +142,17 @@ class TestCmdZset(unittest.TestCase):
assert l.zttl('a') == -1 assert l.zttl('a') == -1
def test_zexpireat_datetime(self): def test_zexpireat_datetime(self):
expire_at = current_time() + datetime.timedelta(minutes=1)
l.zadd('a', a1=1) l.zadd('a', a1=1)
assert l.zexpireat('a', expire_at) assert l.zexpireat('a', expire_at())
assert 0 < l.zttl('a') <= 61 assert 0 < l.zttl('a') <= 61
def test_zexpireat_unixtime(self): def test_zexpireat_unixtime(self):
expire_at = current_time() + datetime.timedelta(minutes=1)
l.zadd('a', a1=1) l.zadd('a', a1=1)
expire_at_seconds = int(time.mktime(expire_at.timetuple())) assert l.zexpireat('a', expire_at_seconds())
assert l.zexpireat('a', expire_at_seconds)
assert 0 < l.zttl('a') <= 61 assert 0 < l.zttl('a') <= 61
def test_zexpireat_no_key(self): def test_zexpireat_no_key(self):
expire_at = current_time() + datetime.timedelta(minutes=1) assert not l.zexpireat('a', expire_at())
assert not l.zexpireat('a', expire_at)
def test_zttl_and_zpersist(self): def test_zttl_and_zpersist(self):
l.zadd('a', a1=1) l.zadd('a', a1=1)

View File

@ -0,0 +1,20 @@
#coding: utf-8
import datetime
import time
def current_time():
return datetime.datetime.now()
def expire_at(minute=1):
expire_at = current_time() + datetime.timedelta(minutes=minute)
return expire_at
def expire_at_seconds(minute=1):
return int(time.mktime(expire_at(minute=minute).timetuple()))
if __name__ == "__main__":
print expire_at()
print expire_at_seconds()

View File

@ -36,16 +36,38 @@ client.bget("bit key 3", function(err, result){
}); });
//test zunionstore & zinterstore //test zunionstore & zinterstore
client.zadd("zset1", 1, "one") client.zadd("zset1", 1, "one");
client.zadd("zset1", 2, "two") client.zadd("zset1", 2, "two");
client.zadd("zset2", 1, "one") client.zadd("zset2", 1, "one");
client.zadd("zset2", 2, "two") client.zadd("zset2", 2, "two");
client.zadd("zset2", 3, "three") client.zadd("zset2", 3, "three");
client.zunionstore("out", 2, "zset1", "zset2", "weights", 2, 3, ledis.print);
client.zrange("out", 0, -1, "withscores", ledis.print);
client.zinterstore("out", 2, "zset1", "zset2", "weights", 2, 3, ledis.print);
client.zrange("out", 0, -1, "withscores", ledis.print);
//example of set commands
client.sadd("a", 1, 2, 3);
client.sadd("b", 3, 4, 5);
client.sismember("a", 1, ledis.print);
client.smembers("a", ledis.print);
client.sdiff("a", "b", ledis.print);
client.sdiffstore("c", "a", "b", ledis.print);
client.sinter("a", "b", ledis.print);
client.sinterstore("c", "a", "b", ledis.print);
client.sunion("a", "b", ledis.print);
client.sunionstore("c", "a", "b", ledis.print);
client.srem("a", 1, ledis.print);
client.sclear("c", ledis.print);
client.smclear("a", "b", ledis.print);
client.sexpire("a", 100, ledis.print);
client.sexpireat("a", 1577808000, ledis.print);
client.sttl("a", ledis.print);
client.spersist("a", ledis.print);
client.zunionstore("out", 2, "zset1", "zset2", "weights", 2, 3, ledis.print)
client.zrange("out", 0, -1, "withscores", ledis.print)
client.zinterstore("out", 2, "zset1", "zset2", "weights", 2, 3, ledis.print)
client.zrange("out", 0, -1, "withscores", ledis.print)
client.quit() client.quit()

View File

@ -2,6 +2,10 @@
module.exports = [ module.exports = [
"quit", "quit",
"ping",
"echo",
"select",
"bget", "bget",
"bdelete", "bdelete",
"bsetbit", "bsetbit",
@ -93,4 +97,26 @@ module.exports = [
"zexpireat", "zexpireat",
"zttl", "zttl",
"zpersist", "zpersist",
"sadd",
"scard",
"sdiff",
"sdiffstore",
"sinter",
"sinterstore",
"sismember",
"smembers",
"srem",
"sunion",
"sunionstore",
"sclear",
"smclear",
"sexpire",
"sexpireat",
"sttl",
"spersist"
]; ];

View File

@ -113,6 +113,27 @@ local commands = {
"bttl", "bttl",
"bpersist", "bpersist",
--[[set]]
"sadd",
"scard",
"sdiff",
"sdiffstore",
"sinter",
"sinterstore",
"sismember",
"smembers",
"srem",
"sunion",
"sunionstore",
"sclear",
"smclear",
"sexpire",
"sexpireat",
"sttl",
"spersist",
--[[server]] --[[server]]
"ping", "ping",
"echo", "echo",

View File

@ -1,56 +1,96 @@
//This file was generated by ./generate.py on Fri Aug 15 2014 16:40:03 +0800
package main package main
var helpCommands = [][]string{ 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"}, {"DECR", "key", "KV"},
{"DECRBY", "key decrement", "KV"}, {"DECRBY", "key decrement", "KV"},
{"DEL", "key [key ...]", "KV"}, {"DEL", "key [key ...]", "KV"},
{"ECHO", "message", "Server"},
{"EXISTS", "key", "KV"}, {"EXISTS", "key", "KV"},
{"EXPIRE", "key seconds", "KV"}, {"EXPIRE", "key seconds", "KV"},
{"EXPIREAT", "key timestamp", "KV"}, {"EXPIREAT", "key timestamp", "KV"},
{"FULLSYNC", "-", "Replication"},
{"GET", "key", "KV"}, {"GET", "key", "KV"},
{"GETSET", " key value", "KV"}, {"GETSET", " key value", "KV"},
{"INCR", "key", "KV"}, {"HCLEAR", "key", "Hash"},
{"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"},
{"HDEL", "key field [field ...]", "Hash"}, {"HDEL", "key field [field ...]", "Hash"},
{"HEXISTS", "key field", "Hash"}, {"HEXISTS", "key field", "Hash"},
{"HEXPIRE", "key seconds", "Hash"},
{"HEXPIREAT", "key timestamp", "Hash"},
{"HGET", "key field", "Hash"}, {"HGET", "key field", "Hash"},
{"HGETALL", "key", "Hash"}, {"HGETALL", "key", "Hash"},
{"HINCRBY", "key field increment", "Hash"}, {"HINCRBY", "key field increment", "Hash"},
{"HKEYS", "key", "Hash"}, {"HKEYS", "key", "Hash"},
{"HLEN", "key", "Hash"}, {"HLEN", "key", "Hash"},
{"HMCLEAR", "key [key ...]", "Hash"},
{"HMGET", "key field [field ...]", "Hash"}, {"HMGET", "key field [field ...]", "Hash"},
{"HMSET", "key field value [field value ...]", "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"}, {"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"}, {"LINDEX", "key index", "List"},
{"LLEN", "key", "List"}, {"LLEN", "key", "List"},
{"LMCLEAR", "key [key ...]", "List"},
{"LPERSIST", "key", "List"},
{"LPOP", "key", "List"}, {"LPOP", "key", "List"},
{"LPUSH", "key value [value ...]", "List"}, {"LPUSH", "key value [value ...]", "List"},
{"LRANGE", "key start stop", "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"}, {"RPOP", "key", "List"},
{"RPUSH", "key value [value ...]", "List"}, {"RPUSH", "key value [value ...]", "List"},
{"LCLEAR", "key", "List"}, {"SADD", "key member [member ...]", "Set"},
{"LMCLEAR", "key [key ...]", "List"}, {"SCARD", "key", "Set"},
{"LEXPIRE", "key seconds", "List"}, {"SCLEAR", "key", "Set"},
{"LEXPIREAT", "key timestamp", "List"}, {"SDIFF", "key [key ...]", "Set"},
{"LTTL", "key", "List"}, {"SDIFFSTORE", "destination key [key ...]", "Set"},
{"LPERSIST", "key", "List"}, {"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"}, {"ZADD", "key score member [score member ...]", "ZSet"},
{"ZCARD", "key", "ZSet"}, {"ZCARD", "key", "ZSet"},
{"ZCLEAR", "key", "ZSet"},
{"ZCOUNT", "key min max", "ZSet"}, {"ZCOUNT", "key min max", "ZSet"},
{"ZEXPIRE", "key seconds", "ZSet"},
{"ZEXPIREAT", "key timestamp", "ZSet"},
{"ZINCRBY", "key increment member", "ZSet"}, {"ZINCRBY", "key increment member", "ZSet"},
{"ZMCLEAR", "key [key ...]", "ZSet"},
{"ZPERSIST", "key", "ZSet"},
{"ZRANGE", "key start stop [WITHSCORES]", "ZSet"}, {"ZRANGE", "key start stop [WITHSCORES]", "ZSet"},
{"ZRANGEBYSCORE", "key min max [WITHSCORES] [LIMIT offset count]", "ZSet"}, {"ZRANGEBYSCORE", "key min max [WITHSCORES] [LIMIT offset count]", "ZSet"},
{"ZRANK", "key member", "ZSet"}, {"ZRANK", "key member", "ZSet"},
@ -61,27 +101,5 @@ var helpCommands = [][]string{
{"ZREVRANGEBYSCORE", "key max min [WITHSCORES][LIMIT offset count]", "ZSet"}, {"ZREVRANGEBYSCORE", "key max min [WITHSCORES][LIMIT offset count]", "ZSet"},
{"ZREVRANK", "key member", "ZSet"}, {"ZREVRANK", "key member", "ZSet"},
{"ZSCORE", "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"}, {"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"},
} }

View File

@ -310,6 +310,91 @@
"group": "Replication", "group": "Replication",
"readonly": false "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": { "TTL": {
"arguments": "key", "arguments": "key",
"group": "KV", "group": "KV",

View File

@ -58,6 +58,25 @@ Table of Contents
- [LEXPIREAT key timestamp](#lexpireat-key-timestamp) - [LEXPIREAT key timestamp](#lexpireat-key-timestamp)
- [LTTL key](#lttl-key) - [LTTL key](#lttl-key)
- [LPERSIST key](#lpersist-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) - [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)
@ -665,7 +684,7 @@ ledis> HVALS myhash
### HCLEAR key ### HCLEAR key
Deletes the specified hash keys Deletes the specified hash key
**Return value** **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 keys 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 ## ZSet
### ZADD key score member [score member ...] ### ZADD key score member [score member ...]
@ -1114,13 +1554,13 @@ The number of elements added to the sorted sets, **not** including elements alre
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 'one' ledis> ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZADD myset 1 'uno' ledis> ZADD myzset 1 'uno'
(integer) 1 (integer) 1
ledis> ZADD myset 2 'two' 3 'three' ledis> ZADD myzset 2 'two' 3 'three'
(integer) 2 (integer) 2
ledis> ZRANGE myset 0 -1 WITHSCORES ledis> ZRANGE myzset 0 -1 WITHSCORES
1) "one" 1) "one"
2) "1" 2) "1"
3) "uno" 3) "uno"
@ -1141,13 +1581,13 @@ int64: the cardinality (number of elements) of the sorted set, or `0` if key doe
**Examples** **Examples**
``` ```
edis > ZADD myset 1 'one' edis > ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZADD myset 1 'uno' ledis> ZADD myzset 1 'uno'
(integer) 1 (integer) 1
ledis> ZADD myset 2 'two' 3 'three' ledis> ZADD myzset 2 'two' 3 'three'
(integer) 2 (integer) 2
ledis> ZRANGE myset 0 -1 WITHSCORES ledis> ZRANGE myzset 0 -1 WITHSCORES
1) "one" 1) "one"
2) "1" 2) "1"
3) "uno" 3) "uno"
@ -1156,7 +1596,7 @@ ledis> ZRANGE myset 0 -1 WITHSCORES
6) "2" 6) "2"
7) "three" 7) "three"
8) "3" 8) "3"
ledis> ZCARD myset ledis> ZCARD myzset
(integer) 4 (integer) 4
``` ```
@ -1171,13 +1611,13 @@ int64: the number of elements in the specified score range.
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 'one' ledis> ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZADD myset 1 'uno' ledis> ZADD myzset 1 'uno'
(integer) 1 (integer) 1
ledis> ZADD myset 2 'two' 3 'three' ledis> ZADD myzset 2 'two' 3 'three'
(integer) 2 (integer) 2
ledis> ZRANGE myset 0 -1 WITHSCORES ledis> ZRANGE myzset 0 -1 WITHSCORES
1) "one" 1) "one"
2) "1" 2) "1"
3) "uno" 3) "uno"
@ -1186,9 +1626,9 @@ ledis> ZRANGE myset 0 -1 WITHSCORES
6) "2" 6) "2"
7) "three" 7) "three"
8) "3" 8) "3"
ledis> ZCOUNT myset -inf +inf ledis> ZCOUNT myzset -inf +inf
(integer) 4 (integer) 4
ledis> ZCOUNT myset (1 3 ledis> ZCOUNT myzset (1 3
(integer) 2 (integer) 2
``` ```
@ -1205,13 +1645,13 @@ bulk: the new score of member (an int64 number), represented as string.
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 'one' ledis> ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZADD myset 2 'two' ledis> ZADD myzset 2 'two'
(integer) 1 (integer) 1
ledis> ZINCRBY myset 2 'one' ledis> ZINCRBY myzset 2 'one'
3 3
ledis> ZRANGE myset 0 -1 WITHSCORES ledis> ZRANGE myzset 0 -1 WITHSCORES
1) "two" 1) "two"
2) "2" 2) "2"
3) "one" 3) "one"
@ -1228,19 +1668,19 @@ array: list of elements in the specified range (optionally with their scores).
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 'one' ledis> ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZADD myset 2 'two' ledis> ZADD myzset 2 'two'
(integer) 1 (integer) 1
ledis> ZADD myset 3 'three' ledis> ZADD myzset 3 'three'
(integer) 1 (integer) 1
ledis> ZRANGE myset 0 -1 ledis> ZRANGE myzset 0 -1
1) "one" 1) "one"
2) "two" 2) "two"
3) "three" 3) "three"
ledis> ZRANGE myset 2 3 ledis> ZRANGE myzset 2 3
1) "three" 1) "three"
ledis> ZRANGE myset -2 -1 ledis> ZRANGE myzset -2 -1
1) "two" 1) "two"
2) "three" 2) "three"
``` ```
@ -1340,16 +1780,16 @@ The number of members removed from the sorted set, not including non existing me
**Examples** **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 (integer) 3
ledis> ZRANGE myset 0 -1 ledis> ZRANGE myzset 0 -1
1) "one" 1) "one"
2) "two" 2) "two"
3) "three" 3) "three"
4) "four" 4) "four"
ledis> ZREM myset three ledis> ZREM myzset three
(integer) 1 (integer) 1
ledis> ZREM myset one four three ledis> ZREM myzset one four three
(integer) 2 (integer) 2
``` ```
@ -1363,11 +1803,11 @@ int64: the number of elements removed.
**Examples** **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 (integer) 3
ledis> ZREMRANGEBYRANK myset 0 2 ledis> ZREMRANGEBYRANK myzset 0 2
(integer) 3 (integer) 3
ledis> ZRANGE myset 0 -1 WITHSCORES ledis> ZRANGE myzset 0 -1 WITHSCORES
1) "four" 1) "four"
2) "4" 2) "4"
``` ```
@ -1383,11 +1823,11 @@ int64: the number of elements removed.
**Examples** **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 (integer) 4
ledis> ZREMRANGEBYSCORE myset -inf (2 ledis> ZREMRANGEBYSCORE myzset -inf (2
(integer) 1 (integer) 1
ledis> ZRANGE myset 0 -1 WITHSCORES ledis> ZRANGE myzset 0 -1 WITHSCORES
1) "two" 1) "two"
2) "2" 2) "2"
3) "three" 3) "three"
@ -1407,9 +1847,9 @@ array: list of elements in the specified range (optionally with their scores).
**Examples** **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 (integer) 4
ledis> ZREVRANGE myset 0 -1 ledis> ZREVRANGE myzset 0 -1
1) "four" 1) "four"
2) "three" 2) "three"
3) "two" 3) "two"
@ -1428,21 +1868,21 @@ array: list of elements in the specified score range (optionally with their scor
**Examples** **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 (integer) 4
ledis> ZREVRANGEBYSCORE myset +inf -inf ledis> ZREVRANGEBYSCORE myzset +inf -inf
1) "four" 1) "four"
2) "three" 2) "three"
3) "two" 3) "two"
4) "one" 4) "one"
ledis> ZREVRANGEBYSCORE myset 2 1 ledis> ZREVRANGEBYSCORE myzset 2 1
1) "two" 1) "two"
2) "one" 2) "one"
ledis> ZREVRANGEBYSCORE myset 2 (1 ledis> ZREVRANGEBYSCORE myzset 2 (1
1) "two" 1) "two"
ledis> ZREVRANGEBYSCORE myset (2 (1 ledis> ZREVRANGEBYSCORE myzset (2 (1
(empty list or set) (empty list or set)
ledis> ZREVRANGEBYSCORE myset +inf -inf WITHSCORES LIMIT 1 2 ledis> ZREVRANGEBYSCORE myzset +inf -inf WITHSCORES LIMIT 1 2
1) "three" 1) "three"
2) "3" 2) "3"
3) "two" 3) "two"
@ -1462,13 +1902,13 @@ Use ZRANK to get the rank of an element with the scores ordered from low to high
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 one ledis> ZADD myzset 1 one
(integer) 1 (integer) 1
ledis> ZADD myset 2 two ledis> ZADD myzset 2 two
(integer) 1 (integer) 1
ledis> ZREVRANK myset one ledis> ZREVRANK myzset one
(integer) 1 (integer) 1
ledis> ZREVRANK myset three ledis> ZREVRANK myzset three
(nil) (nil)
``` ```
@ -1484,9 +1924,9 @@ bulk: the score of member (an `int64` number), represented as string.
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 'one' ledis> ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZSCORE myset 'one' ledis> ZSCORE myzset 'one'
1 1
``` ```
@ -1500,17 +1940,17 @@ int64: the number of members in the zset stored at key
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 'one' ledis> ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZADD myset 2 'two' ledis> ZADD myzset 2 'two'
(integer) 1 (integer) 1
ledis> ZADD myset 3 'three' ledis> ZADD myzset 3 'three'
(integer) 1 (integer) 1
ledis> ZRANGE myset 0 -1 ledis> ZRANGE myzset 0 -1
1) "one" 1) "one"
2) "two" 2) "two"
3) "three" 3) "three"
ledis> ZCLEAR myset ledis> ZCLEAR myzset
(integer) 3 (integer) 3
``` ```
@ -1524,11 +1964,11 @@ int64: the number of input keys
**Examples** **Examples**
``` ```
ledis> ZADD myset1 1 'one' ledis> ZADD myzset1 1 'one'
(integer) 1 (integer) 1
ledis> ZADD myset2 2 'two' ledis> ZADD myzset2 2 'two'
(integer) 1 (integer) 1
ledis> ZMCLEAR myset1 myset2 ledis> ZMCLEAR myzset1 myzset2
(integer) 2 (integer) 2
``` ```
@ -1547,17 +1987,17 @@ int64:
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 'one' ledis> ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZEXPIRE myset 100 ledis> ZEXPIRE myzset 100
(integer) 1 (integer) 1
ledis> ZTTL myset ledis> ZTTL myzset
(integer) 97 (integer) 97
ledis> ZPERSIST myset ledis> ZPERSIST myzset
(integer) 1 (integer) 1
ledis> ZTTL mset ledis> ZTTL mset
(integer) -1 (integer) -1
ledis> ZEXPIRE myset1 100 ledis> ZEXPIRE myzset1 100
(integer) 0 (integer) 0
``` ```
@ -1574,17 +2014,17 @@ int64:
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 'one' ledis> ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZEXPIREAT myset 1404149999 ledis> ZEXPIREAT myzset 1404149999
(integer) 1 (integer) 1
ledis> ZTTL myset ledis> ZTTL myzset
(integer) 7155 (integer) 7155
ledis> ZPERSIST myset ledis> ZPERSIST myzset
(integer) 1 (integer) 1
ledis> ZTTL mset ledis> ZTTL mset
(integer) -1 (integer) -1
ledis> ZEXPIREAT myset1 1404149999 ledis> ZEXPIREAT myzset1 1404149999
(integer) 0 (integer) 0
``` ```
@ -1599,13 +2039,13 @@ int64: TTL in seconds
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 'one' ledis> ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZEXPIRE myset 100 ledis> ZEXPIRE myzset 100
(integer) 1 (integer) 1
ledis> ZTTL myset ledis> ZTTL myzset
(integer) 97 (integer) 97
ledis> ZTTL myset2 ledis> ZTTL myzset2
(integer) -1 (integer) -1
``` ```
@ -1622,13 +2062,13 @@ int64:
**Examples** **Examples**
``` ```
ledis> ZADD myset 1 'one' ledis> ZADD myzset 1 'one'
(integer) 1 (integer) 1
ledis> ZEXPIRE myset 100 ledis> ZEXPIRE myzset 100
(integer) 1 (integer) 1
ledis> ZTTL myset ledis> ZTTL myzset
(integer) 97 (integer) 97
ledis> ZPERSIST myset ledis> ZPERSIST myzset
(integer) 1 (integer) 1
ledis> ZTTL mset ledis> ZTTL mset
(integer) -1 (integer) -1

View File

@ -16,6 +16,8 @@ const (
ZScoreType byte = 8 ZScoreType byte = 8
BitType byte = 9 BitType byte = 9
BitMetaType byte = 10 BitMetaType byte = 10
SetType byte = 11
SSizeType byte = 12
maxDataType byte = 100 maxDataType byte = 100
@ -35,6 +37,8 @@ var (
ZScoreType: "zscore", ZScoreType: "zscore",
BitType: "bit", BitType: "bit",
BitMetaType: "bitmeta", BitMetaType: "bitmeta",
SetType: "set",
SSizeType: "ssize",
ExpTimeType: "exptime", ExpTimeType: "exptime",
ExpMetaType: "expmeta", ExpMetaType: "expmeta",
} }
@ -48,6 +52,7 @@ var (
errKeySize = errors.New("invalid key size") errKeySize = errors.New("invalid key size")
errValueSize = errors.New("invalid value size") errValueSize = errors.New("invalid value size")
errHashFieldSize = errors.New("invalid hash field size") errHashFieldSize = errors.New("invalid hash field size")
errSetMemberSize = errors.New("invalid set member size")
errZSetMemberSize = errors.New("invalid zset member size") errZSetMemberSize = errors.New("invalid zset member size")
errExpireValue = errors.New("invalid expire value") errExpireValue = errors.New("invalid expire value")
) )
@ -65,6 +70,9 @@ const (
//max zset member size //max zset member size
MaxZSetMemberSize int = 1024 MaxZSetMemberSize int = 1024
//max set member size
MaxSetMemberSize int = 1024
//max value size //max value size
MaxValueSize int = 10 * 1024 * 1024 MaxValueSize int = 10 * 1024 * 1024
) )

View File

@ -1,6 +1,6 @@
// Package ledis is a high performance embedded NoSQL. // Package ledis is a high performance embedded NoSQL.
// //
// Ledis supports various advanced data structure like kv, list, hash and zset like redis. // Ledis supports various data structure like kv, list, hash and zset like redis.
// //
// Other features include binlog replication, data with a limited time-to-live. // Other features include binlog replication, data with a limited time-to-live.
// //

View File

@ -21,6 +21,7 @@ type DB struct {
hashTx *tx hashTx *tx
zsetTx *tx zsetTx *tx
binTx *tx binTx *tx
setTx *tx
} }
type Ledis struct { type Ledis struct {
@ -88,6 +89,7 @@ func newDB(l *Ledis, index uint8) *DB {
d.hashTx = newTx(l) d.hashTx = newTx(l)
d.zsetTx = newTx(l) d.zsetTx = newTx(l)
d.binTx = newTx(l) d.binTx = newTx(l)
d.setTx = newTx(l)
return d return d
} }

View File

@ -1,6 +1,7 @@
package ledis package ledis
import ( import (
"fmt"
"github.com/siddontang/ledisdb/store" "github.com/siddontang/ledisdb/store"
) )
@ -10,7 +11,8 @@ func (db *DB) FlushAll() (drop int64, err error) {
db.lFlush, db.lFlush,
db.hFlush, db.hFlush,
db.zFlush, db.zFlush,
db.bFlush} db.bFlush,
db.sFlush}
for _, flush := range all { for _, flush := range all {
if n, e := flush(); e != nil { if n, e := flush(); e != nil {
@ -49,3 +51,48 @@ func (db *DB) flushRegion(t *tx, minKey []byte, maxKey []byte) (drop int64, err
it.Close() it.Close()
return return
} }
func (db *DB) flushType(t *tx, dataType byte) (drop int64, err error) {
var deleteFunc func(t *tx, key []byte) int64
var metaDataType byte
switch dataType {
case KVType:
deleteFunc = db.delete
metaDataType = KVType
case ListType:
deleteFunc = db.lDelete
metaDataType = LMetaType
case HashType:
deleteFunc = db.hDelete
metaDataType = HSizeType
case ZSetType:
deleteFunc = db.zDelete
metaDataType = ZSizeType
case BitType:
deleteFunc = db.bDelete
metaDataType = BitMetaType
case SetType:
deleteFunc = db.sDelete
metaDataType = SSizeType
default:
return 0, fmt.Errorf("invalid data type: %s", TypeName[dataType])
}
var keys [][]byte
keys, err = db.scan(metaDataType, nil, 1024, false)
for len(keys) != 0 || err != nil {
for _, key := range keys {
deleteFunc(t, key)
db.rmExpire(t, dataType, key)
}
if err = t.Commit(); err != nil {
return
} else {
drop += int64(len(keys))
}
keys, err = db.scan(metaDataType, nil, 1024, false)
}
return
}

92
ledis/scan.go Normal file
View File

@ -0,0 +1,92 @@
package ledis
import (
"errors"
"github.com/siddontang/ledisdb/store"
)
var errDataType = errors.New("error data type")
var errMetaKey = errors.New("error meta key")
func (db *DB) scan(dataType byte, key []byte, count int, inclusive bool) ([][]byte, error) {
var minKey, maxKey []byte
var err error
if key != nil {
if err = checkKeySize(key); err != nil {
return nil, err
}
if minKey, err = db.encodeMetaKey(dataType, key); err != nil {
return nil, err
}
} else {
if minKey, err = db.encodeMinKey(dataType); err != nil {
return nil, err
}
}
if maxKey, err = db.encodeMaxKey(dataType); err != nil {
return nil, err
}
if count <= 0 {
count = defaultScanCount
}
v := make([][]byte, 0, count)
rangeType := store.RangeROpen
if !inclusive {
rangeType = store.RangeOpen
}
it := db.db.RangeLimitIterator(minKey, maxKey, rangeType, 0, count)
for ; it.Valid(); it.Next() {
if k, err := db.decodeMetaKey(dataType, it.Key()); err != nil {
continue
} else {
v = append(v, k)
}
}
it.Close()
return v, nil
}
func (db *DB) encodeMinKey(dataType byte) ([]byte, error) {
return db.encodeMetaKey(dataType, nil)
}
func (db *DB) encodeMaxKey(dataType byte) ([]byte, error) {
k, err := db.encodeMetaKey(dataType, nil)
if err != nil {
return nil, err
}
k[len(k)-1] = dataType + 1
return k, nil
}
func (db *DB) encodeMetaKey(dataType byte, key []byte) ([]byte, error) {
switch dataType {
case KVType:
return db.encodeKVKey(key), nil
case LMetaType:
return db.lEncodeMetaKey(key), nil
case HSizeType:
return db.hEncodeSizeKey(key), nil
case ZSizeType:
return db.zEncodeSizeKey(key), nil
case BitMetaType:
return db.bEncodeMetaKey(key), nil
case SSizeType:
return db.sEncodeSizeKey(key), nil
default:
return nil, errDataType
}
}
func (db *DB) decodeMetaKey(dataType byte, ek []byte) ([]byte, error) {
if len(ek) < 2 || ek[0] != db.index || ek[1] != dataType {
return nil, errMetaKey
}
return ek[2:], nil
}

277
ledis/scan_test.go Normal file
View File

@ -0,0 +1,277 @@
package ledis
import (
"testing"
)
func TestDBScan(t *testing.T) {
db := getTestDB()
db.FlushAll()
if v, err := db.Scan(nil, 10, true); err != nil {
t.Fatal(err)
} else if len(v) != 0 {
t.Fatal(len(v))
}
db.Set([]byte("a"), []byte{})
db.Set([]byte("b"), []byte{})
db.Set([]byte("c"), []byte{})
if v, err := db.Scan(nil, 1, true); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal(len(v))
}
if v, err := db.Scan([]byte("a"), 2, false); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal(len(v))
}
if v, err := db.Scan(nil, 3, true); err != nil {
t.Fatal(err)
} else if len(v) != 3 {
t.Fatal(len(v))
}
}
func TestDBHScan(t *testing.T) {
db := getTestDB()
db.hFlush()
k1 := []byte("k1")
db.HSet(k1, []byte("1"), []byte{})
k2 := []byte("k2")
db.HSet(k2, []byte("2"), []byte{})
k3 := []byte("k3")
db.HSet(k3, []byte("3"), []byte{})
if v, err := db.HScan(nil, 1, true); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k1" {
t.Fatal("invalid value ", string(v[0]))
}
if v, err := db.HScan(k1, 2, true); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k1" {
t.Fatal("invalid value ", string(v[0]))
} else if string(v[1]) != "k2" {
t.Fatal("invalid value ", string(v[1]))
}
if v, err := db.HScan(k1, 2, false); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k2" {
t.Fatal("invalid value ", string(v[0]))
} else if string(v[1]) != "k3" {
t.Fatal("invalid value ", string(v[1]))
}
}
func TestDBZScan(t *testing.T) {
db := getTestDB()
db.zFlush()
k1 := []byte("k1")
db.ZAdd(k1, ScorePair{1, []byte("m")})
k2 := []byte("k2")
db.ZAdd(k2, ScorePair{2, []byte("m")})
k3 := []byte("k3")
db.ZAdd(k3, ScorePair{3, []byte("m")})
if v, err := db.ZScan(nil, 1, true); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k1" {
t.Fatal("invalid value ", string(v[0]))
}
if v, err := db.ZScan(k1, 2, true); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k1" {
t.Fatal("invalid value ", string(v[0]))
} else if string(v[1]) != "k2" {
t.Fatal("invalid value ", string(v[1]))
}
if v, err := db.ZScan(k1, 2, false); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k2" {
t.Fatal("invalid value ", string(v[0]))
} else if string(v[1]) != "k3" {
t.Fatal("invalid value ", string(v[1]))
}
}
func TestDBLScan(t *testing.T) {
db := getTestDB()
db.lFlush()
k1 := []byte("k1")
if _, err := db.LPush(k1, []byte("elem")); err != nil {
t.Fatal(err.Error())
}
k2 := []byte("k2")
if _, err := db.LPush(k2, []byte("elem")); err != nil {
t.Fatal(err.Error())
}
k3 := []byte("k3")
if _, err := db.LPush(k3, []byte("elem")); err != nil {
t.Fatal(err.Error())
}
if v, err := db.LScan(nil, 1, true); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k1" {
t.Fatal("invalid value ", string(v[0]))
}
if v, err := db.LScan(k1, 2, true); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k1" {
t.Fatal("invalid value ", string(v[0]))
} else if string(v[1]) != "k2" {
t.Fatal("invalid value ", string(v[1]))
}
if v, err := db.LScan(k1, 2, false); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k2" {
t.Fatal("invalid value ", string(v[0]))
} else if string(v[1]) != "k3" {
t.Fatal("invalid value ", string(v[1]))
}
}
func TestDBBScan(t *testing.T) {
db := getTestDB()
db.bFlush()
k1 := []byte("k1")
if _, err := db.BSetBit(k1, 1, 1); err != nil {
t.Fatal(err.Error())
}
k2 := []byte("k2")
if _, err := db.BSetBit(k2, 1, 1); err != nil {
t.Fatal(err.Error())
}
k3 := []byte("k3")
if _, err := db.BSetBit(k3, 1, 0); err != nil {
t.Fatal(err.Error())
}
if v, err := db.BScan(nil, 1, true); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k1" {
t.Fatal("invalid value ", string(v[0]))
}
if v, err := db.BScan(k1, 2, true); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k1" {
t.Fatal("invalid value ", string(v[0]))
} else if string(v[1]) != "k2" {
t.Fatal("invalid value ", string(v[1]))
}
if v, err := db.BScan(k1, 2, false); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k2" {
t.Fatal("invalid value ", string(v[0]))
} else if string(v[1]) != "k3" {
t.Fatal("invalid value ", string(v[1]))
}
}
func TestDBSScan(t *testing.T) {
db := getTestDB()
db.bFlush()
k1 := []byte("k1")
if _, err := db.SAdd(k1, []byte("1")); err != nil {
t.Fatal(err.Error())
}
k2 := []byte("k2")
if _, err := db.SAdd(k2, []byte("1")); err != nil {
t.Fatal(err.Error())
}
k3 := []byte("k3")
if _, err := db.SAdd(k3, []byte("1")); err != nil {
t.Fatal(err.Error())
}
if v, err := db.SScan(nil, 1, true); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k1" {
t.Fatal("invalid value ", string(v[0]))
}
if v, err := db.SScan(k1, 2, true); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k1" {
t.Fatal("invalid value ", string(v[0]))
} else if string(v[1]) != "k2" {
t.Fatal("invalid value ", string(v[1]))
}
if v, err := db.SScan(k1, 2, false); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal("invalid length ", len(v))
} else if string(v[0]) != "k2" {
t.Fatal("invalid value ", string(v[0]))
} else if string(v[1]) != "k3" {
t.Fatal("invalid value ", string(v[1]))
}
}

View File

@ -908,26 +908,14 @@ func (db *DB) BPersist(key []byte) (int64, error) {
return n, err return n, err
} }
// func (db *DB) BScan(key []byte, count int, inclusive bool) ([]KVPair, error) { func (db *DB) BScan(key []byte, count int, inclusive bool) ([][]byte, error) {
return db.scan(BitMetaType, key, count, inclusive)
// } }
func (db *DB) bFlush() (drop int64, err error) { func (db *DB) bFlush() (drop int64, err error) {
t := db.binTx t := db.binTx
t.Lock() t.Lock()
defer t.Unlock() defer t.Unlock()
minKey := make([]byte, 2) return db.flushType(t, BitType)
minKey[0] = db.index
minKey[1] = BitType
maxKey := make([]byte, 2)
maxKey[0] = db.index
maxKey[1] = BitMetaType + 1
drop, err = db.flushRegion(t, minKey, maxKey)
err = db.expFlush(t, BitType)
err = t.Commit()
return
} }

View File

@ -1,6 +1,7 @@
package ledis package ledis
import ( import (
"encoding/binary"
"testing" "testing"
) )
@ -536,3 +537,56 @@ func testBitExpire(t *testing.T) {
t.Fatal(false) t.Fatal(false)
} }
} }
func testBFlush(t *testing.T) {
db := getTestDB()
db.FlushAll()
for i := 0; i < 2000; i++ {
key := []byte{}
binary.LittleEndian.PutUint32(key, uint32(i))
if _, err := db.BSetBit(key, 1, 1); err != nil {
t.Fatal(err.Error())
}
}
if v, err := db.BScan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 2000 {
t.Fatal("invalid value ", len(v))
}
for i := 0; i < 2000; i++ {
key := []byte{}
binary.LittleEndian.PutUint32(key, uint32(i))
if v, err := db.BGetBit(key, 1); err != nil {
t.Fatal(err.Error())
} else if v != 1 {
t.Fatal("invalid value ", v)
}
}
if n, err := db.bFlush(); err != nil {
t.Fatal(err.Error())
} else if n != 2000 {
t.Fatal("invalid value ", n)
}
if v, err := db.BScan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if v != nil {
t.Fatal("invalid value length ", len(v))
}
for i := 0; i < 2000; i++ {
key := []byte{}
binary.LittleEndian.PutUint32(key, uint32(i))
if v, err := db.BGet(key); err != nil {
t.Fatal(err.Error())
} else if v != nil {
t.Fatal("invalid value ", v)
}
}
}

View File

@ -453,60 +453,14 @@ func (db *DB) HMclear(keys ...[]byte) (int64, error) {
} }
func (db *DB) hFlush() (drop int64, err error) { func (db *DB) hFlush() (drop int64, err error) {
minKey := make([]byte, 2) t := db.hashTx
minKey[0] = db.index
minKey[1] = HashType
maxKey := make([]byte, 2)
maxKey[0] = db.index
maxKey[1] = HSizeType + 1
t := db.kvTx
t.Lock() t.Lock()
defer t.Unlock() defer t.Unlock()
return db.flushType(t, HashType)
drop, err = db.flushRegion(t, minKey, maxKey)
err = db.expFlush(t, HashType)
err = t.Commit()
return
} }
func (db *DB) HScan(key []byte, field []byte, count int, inclusive bool) ([]FVPair, error) { func (db *DB) HScan(key []byte, count int, inclusive bool) ([][]byte, error) {
var minKey []byte return db.scan(HSizeType, key, count, inclusive)
if field != nil {
if err := checkHashKFSize(key, field); err != nil {
return nil, err
}
minKey = db.hEncodeHashKey(key, field)
} else {
minKey = db.hEncodeStartKey(key)
}
maxKey := db.hEncodeStopKey(key)
if count <= 0 {
count = defaultScanCount
}
v := make([]FVPair, 0, count)
rangeType := store.RangeROpen
if !inclusive {
rangeType = store.RangeOpen
}
it := db.db.RangeLimitIterator(minKey, maxKey, rangeType, 0, count)
for ; it.Valid(); it.Next() {
if _, f, err := db.hDecodeHashKey(it.Key()); err != nil {
continue
} else {
v = append(v, FVPair{Field: f, Value: it.Value()})
}
}
it.Close()
return v, nil
} }
func (db *DB) HExpire(key []byte, duration int64) (int64, error) { func (db *DB) HExpire(key []byte, duration int64) (int64, error) {

View File

@ -1,6 +1,7 @@
package ledis package ledis
import ( import (
"fmt"
"testing" "testing"
) )
@ -56,35 +57,6 @@ func TestDBHash(t *testing.T) {
} }
func TestDBHScan(t *testing.T) {
db := getTestDB()
db.hFlush()
key := []byte("a")
db.HSet(key, []byte("1"), []byte{})
db.HSet(key, []byte("2"), []byte{})
db.HSet(key, []byte("3"), []byte{})
if v, err := db.HScan(key, nil, 1, true); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal(len(v))
}
if v, err := db.HScan(key, []byte("1"), 2, false); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal(len(v))
}
if v, err := db.HScan(key, nil, 10, true); err != nil {
t.Fatal(err)
} else if len(v) != 3 {
t.Fatal(len(v))
}
}
func TestHashPersist(t *testing.T) { func TestHashPersist(t *testing.T) {
db := getTestDB() db := getTestDB()
@ -107,3 +79,55 @@ func TestHashPersist(t *testing.T) {
t.Fatal(n) t.Fatal(n)
} }
} }
func TestHFlush(t *testing.T) {
db := getTestDB()
db.FlushAll()
for i := 0; i < 2000; i++ {
key := fmt.Sprintf("%d", i)
if _, err := db.HSet([]byte(key), []byte("f"), []byte("v")); err != nil {
t.Fatal(err.Error())
}
}
if v, err := db.HScan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 2000 {
t.Fatal("invalid value ", len(v))
}
for i := 0; i < 2000; i++ {
key := fmt.Sprintf("%d", i)
if v, err := db.HGet([]byte(key), []byte("f")); err != nil {
t.Fatal(err.Error())
} else if string(v) != "v" {
t.Fatal("invalid value ", v)
}
}
if n, err := db.hFlush(); err != nil {
t.Fatal(err.Error())
} else if n != 2000 {
t.Fatal("invalid value ", n)
}
if v, err := db.HScan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 0 {
t.Fatal("invalid value length ", len(v))
}
for i := 0; i < 2000; i++ {
key := []byte(fmt.Sprintf("%d", i))
if v, err := db.HGet(key, []byte("f")); err != nil {
t.Fatal(err.Error())
} else if v != nil {
t.Fatal("invalid value ", v)
}
}
}

View File

@ -2,7 +2,6 @@ package ledis
import ( import (
"errors" "errors"
"github.com/siddontang/ledisdb/store"
"time" "time"
) )
@ -309,56 +308,15 @@ func (db *DB) SetNX(key []byte, value []byte) (int64, error) {
} }
func (db *DB) flush() (drop int64, err error) { func (db *DB) flush() (drop int64, err error) {
minKey := db.encodeKVMinKey()
maxKey := db.encodeKVMaxKey()
t := db.kvTx t := db.kvTx
t.Lock() t.Lock()
defer t.Unlock() defer t.Unlock()
return db.flushType(t, KVType)
drop, err = db.flushRegion(t, minKey, maxKey)
err = db.expFlush(t, KVType)
err = t.Commit()
return
} }
//if inclusive is true, scan range [key, inf) else (key, inf) //if inclusive is true, scan range [key, inf) else (key, inf)
func (db *DB) Scan(key []byte, count int, inclusive bool) ([]KVPair, error) { func (db *DB) Scan(key []byte, count int, inclusive bool) ([][]byte, error) {
var minKey []byte return db.scan(KVType, key, count, inclusive)
if key != nil {
if err := checkKeySize(key); err != nil {
return nil, err
}
minKey = db.encodeKVKey(key)
} else {
minKey = db.encodeKVMinKey()
}
maxKey := db.encodeKVMaxKey()
if count <= 0 {
count = defaultScanCount
}
v := make([]KVPair, 0, 2*count)
rangeType := store.RangeROpen
if !inclusive {
rangeType = store.RangeOpen
}
it := db.db.RangeLimitIterator(minKey, maxKey, rangeType, 0, count)
for ; it.Valid(); it.Next() {
if key, err := db.decodeKVKey(it.Key()); err != nil {
continue
} else {
v = append(v, KVPair{Key: key, Value: it.Value()})
}
}
it.Close()
return v, nil
} }
func (db *DB) Expire(key []byte, duration int64) (int64, error) { func (db *DB) Expire(key []byte, duration int64) (int64, error) {

View File

@ -1,6 +1,7 @@
package ledis package ledis
import ( import (
"fmt"
"testing" "testing"
) )
@ -43,40 +44,6 @@ func TestDBKV(t *testing.T) {
} }
func TestDBScan(t *testing.T) {
db := getTestDB()
db.FlushAll()
if v, err := db.Scan(nil, 10, true); err != nil {
t.Fatal(err)
} else if len(v) != 0 {
t.Fatal(len(v))
}
db.Set([]byte("a"), []byte{})
db.Set([]byte("b"), []byte{})
db.Set([]byte("c"), []byte{})
if v, err := db.Scan(nil, 1, true); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal(len(v))
}
if v, err := db.Scan([]byte("a"), 2, false); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal(len(v))
}
if v, err := db.Scan(nil, 3, true); err != nil {
t.Fatal(err)
} else if len(v) != 3 {
t.Fatal(len(v))
}
}
func TestKVPersist(t *testing.T) { func TestKVPersist(t *testing.T) {
db := getTestDB() db := getTestDB()
@ -99,3 +66,53 @@ func TestKVPersist(t *testing.T) {
t.Fatal(n) t.Fatal(n)
} }
} }
func TestKVFlush(t *testing.T) {
db := getTestDB()
db.FlushAll()
for i := 0; i < 2000; i++ {
key := fmt.Sprintf("%d", i)
if err := db.Set([]byte(key), []byte("v")); err != nil {
t.Fatal(err.Error())
}
}
if v, err := db.Scan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 2000 {
t.Fatal("invalid value ", len(v))
}
for i := 0; i < 2000; i++ {
key := fmt.Sprintf("%d", i)
if v, err := db.Get([]byte(key)); err != nil {
t.Fatal(err.Error())
} else if string(v) != "v" {
t.Fatal("invalid value ", v)
}
}
if n, err := db.flush(); err != nil {
t.Fatal(err.Error())
} else if n != 2000 {
t.Fatal("invalid value ", n)
}
if v, err := db.Scan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 0 {
t.Fatal("invalid value length ", len(v))
}
for i := 0; i < 2000; i++ {
key := []byte(fmt.Sprintf("%d", i))
if v, err := db.Get(key); err != nil {
t.Fatal(err.Error())
} else if v != nil {
t.Fatal("invalid value ", v)
}
}
}

View File

@ -424,23 +424,10 @@ func (db *DB) LMclear(keys ...[]byte) (int64, error) {
} }
func (db *DB) lFlush() (drop int64, err error) { func (db *DB) lFlush() (drop int64, err error) {
minKey := make([]byte, 2)
minKey[0] = db.index
minKey[1] = ListType
maxKey := make([]byte, 2)
maxKey[0] = db.index
maxKey[1] = LMetaType + 1
t := db.listTx t := db.listTx
t.Lock() t.Lock()
defer t.Unlock() defer t.Unlock()
return db.flushType(t, ListType)
drop, err = db.flushRegion(t, minKey, maxKey)
err = db.expFlush(t, ListType)
err = t.Commit()
return
} }
func (db *DB) LExpire(key []byte, duration int64) (int64, error) { func (db *DB) LExpire(key []byte, duration int64) (int64, error) {
@ -484,3 +471,17 @@ func (db *DB) LPersist(key []byte) (int64, error) {
err = t.Commit() err = t.Commit()
return n, err return n, err
} }
func (db *DB) LScan(key []byte, count int, inclusive bool) ([][]byte, error) {
return db.scan(LMetaType, key, count, inclusive)
}
func (db *DB) lEncodeMinKey() []byte {
return db.lEncodeMetaKey(nil)
}
func (db *DB) lEncodeMaxKey() []byte {
ek := db.lEncodeMetaKey(nil)
ek[len(ek)-1] = LMetaType + 1
return ek
}

View File

@ -1,6 +1,7 @@
package ledis package ledis
import ( import (
"fmt"
"testing" "testing"
) )
@ -100,3 +101,58 @@ func TestListPersist(t *testing.T) {
t.Fatal(n) t.Fatal(n)
} }
} }
func TestLFlush(t *testing.T) {
db := getTestDB()
db.FlushAll()
for i := 0; i < 2000; i++ {
key := fmt.Sprintf("%d", i)
if _, err := db.LPush([]byte(key), []byte("v")); err != nil {
t.Fatal(err.Error())
}
}
if v, err := db.LScan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 2000 {
t.Fatal("invalid value ", len(v))
}
if n, err := db.lFlush(); err != nil {
t.Fatal(err.Error())
} else if n != 2000 {
t.Fatal("invalid value ", n)
}
if v, err := db.LScan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 0 {
t.Fatal("invalid value length ", len(v))
}
for i := 0; i < 2000; i++ {
key := fmt.Sprintf("%d", i)
if _, err := db.ZAdd([]byte(key), ScorePair{1, []byte("v")}); err != nil {
t.Fatal(err.Error())
}
}
if v, err := db.ZScan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 2000 {
t.Fatal("invalid value ", len(v))
}
if n, err := db.zFlush(); err != nil {
t.Fatal(err.Error())
} else if n != 2000 {
t.Fatal("invalid value ", n)
}
if v, err := db.ZScan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 0 {
t.Fatal("invalid value length ", len(v))
}
}

600
ledis/t_set.go Normal file
View File

@ -0,0 +1,600 @@
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) {
t := db.setTx
t.Lock()
defer t.Unlock()
return db.flushType(t, SetType)
}
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 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 _, err := db.db.Get(ek); err != nil {
return 0, err
}
t.Put(ek, nil)
}
var num = int64(len(v))
sk := db.sEncodeSizeKey(dstKey)
t.Put(sk, PutInt64(num))
if err = t.Commit(); err != nil {
return 0, err
}
return num, nil
}
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
}
func (db *DB) SScan(key []byte, count int, inclusive bool) ([][]byte, error) {
return db.scan(SSizeType, key, count, inclusive)
}

373
ledis/t_set_test.go Normal file
View File

@ -0,0 +1,373 @@
package ledis
import (
"fmt"
"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, m2, 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")
db.SAdd(dstkey, []byte("x"))
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)
}
}
func TestSFlush(t *testing.T) {
db := getTestDB()
db.FlushAll()
for i := 0; i < 2000; i++ {
key := fmt.Sprintf("%d", i)
if _, err := db.SAdd([]byte(key), []byte("v")); err != nil {
t.Fatal(err.Error())
}
}
if v, err := db.SScan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 2000 {
t.Fatal("invalid value ", len(v))
}
if n, err := db.sFlush(); err != nil {
t.Fatal(err.Error())
} else if n != 2000 {
t.Fatal("invalid value ", n)
}
if v, err := db.SScan(nil, 3000, true); err != nil {
t.Fatal(err.Error())
} else if len(v) != 0 {
t.Fatal("invalid value length ", len(v))
}
}

View File

@ -738,71 +738,7 @@ func (db *DB) zFlush() (drop int64, err error) {
t := db.zsetTx t := db.zsetTx
t.Lock() t.Lock()
defer t.Unlock() defer t.Unlock()
return db.flushType(t, ZSetType)
minKey := make([]byte, 2)
minKey[0] = db.index
minKey[1] = ZSetType
maxKey := make([]byte, 2)
maxKey[0] = db.index
maxKey[1] = ZScoreType + 1
it := db.db.RangeLimitIterator(minKey, maxKey, store.RangeROpen, 0, -1)
defer it.Close()
for ; it.Valid(); it.Next() {
t.Delete(it.RawKey())
drop++
if drop&1023 == 0 {
if err = t.Commit(); err != nil {
return
}
}
}
db.expFlush(t, ZSetType)
err = t.Commit()
return
}
func (db *DB) ZScan(key []byte, member []byte, count int, inclusive bool) ([]ScorePair, error) {
var minKey []byte
if member != nil {
if err := checkZSetKMSize(key, member); err != nil {
return nil, err
}
minKey = db.zEncodeSetKey(key, member)
} else {
minKey = db.zEncodeStartSetKey(key)
}
maxKey := db.zEncodeStopSetKey(key)
if count <= 0 {
count = defaultScanCount
}
v := make([]ScorePair, 0, 2*count)
rangeType := store.RangeROpen
if !inclusive {
rangeType = store.RangeOpen
}
it := db.db.RangeLimitIterator(minKey, maxKey, rangeType, 0, count)
for ; it.Valid(); it.Next() {
if _, m, err := db.zDecodeSetKey(it.Key()); err != nil {
continue
} else {
score, _ := Int64(it.Value(), nil)
v = append(v, ScorePair{Member: m, Score: score})
}
}
it.Close()
return v, nil
} }
func (db *DB) ZExpire(key []byte, duration int64) (int64, error) { func (db *DB) ZExpire(key []byte, duration int64) (int64, error) {
@ -912,29 +848,25 @@ func (db *DB) ZUnionStore(destKey []byte, srcKeys [][]byte, weights []int64, agg
db.zDelete(t, destKey) db.zDelete(t, destKey)
var num int64 = 0
for member, score := range destMap { for member, score := range destMap {
if err := checkZSetKMSize(destKey, []byte(member)); err != nil { if err := checkZSetKMSize(destKey, []byte(member)); err != nil {
return 0, err return 0, err
} }
if n, err := db.zSetItem(t, destKey, score, []byte(member)); err != nil { if _, err := db.zSetItem(t, destKey, score, []byte(member)); err != nil {
return 0, err return 0, err
} else if n == 0 {
//add new
num++
} }
} }
if _, err := db.zIncrSize(t, destKey, num); err != nil { var num = int64(len(destMap))
return 0, err sk := db.zEncodeSizeKey(destKey)
} t.Put(sk, PutInt64(num))
//todo add binlog //todo add binlog
if err := t.Commit(); err != nil { if err := t.Commit(); err != nil {
return 0, err return 0, err
} }
return int64(len(destMap)), nil return num, nil
} }
func (db *DB) ZInterStore(destKey []byte, srcKeys [][]byte, weights []int64, aggregate byte) (int64, error) { func (db *DB) ZInterStore(destKey []byte, srcKeys [][]byte, weights []int64, aggregate byte) (int64, error) {
@ -986,26 +918,25 @@ func (db *DB) ZInterStore(destKey []byte, srcKeys [][]byte, weights []int64, agg
db.zDelete(t, destKey) db.zDelete(t, destKey)
var num int64 = 0
for member, score := range destMap { for member, score := range destMap {
if err := checkZSetKMSize(destKey, []byte(member)); err != nil { if err := checkZSetKMSize(destKey, []byte(member)); err != nil {
return 0, err return 0, err
} }
if _, err := db.zSetItem(t, destKey, score, []byte(member)); err != nil {
if n, err := db.zSetItem(t, destKey, score, []byte(member)); err != nil {
return 0, err return 0, err
} else if n == 0 {
//add new
num++
} }
} }
if _, err := db.zIncrSize(t, destKey, num); err != nil { var num int64 = int64(len(destMap))
return 0, err sk := db.zEncodeSizeKey(destKey)
} t.Put(sk, PutInt64(num))
//todo add binlog //todo add binlog
if err := t.Commit(); err != nil { if err := t.Commit(); err != nil {
return 0, err return 0, err
} }
return int64(len(destMap)), nil return num, nil
}
func (db *DB) ZScan(key []byte, count int, inclusive bool) ([][]byte, error) {
return db.scan(ZSizeType, key, count, inclusive)
} }

View File

@ -215,33 +215,6 @@ func TestZSetOrder(t *testing.T) {
return return
} }
func TestDBZScan(t *testing.T) {
db := getTestDB()
db.zFlush()
key := []byte("key")
db.ZAdd(key, pair("a", 0), pair("b", 1), pair("c", 2))
if v, err := db.ZScan(key, nil, 1, true); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal(len(v))
}
if v, err := db.ZScan(key, []byte("a"), 2, false); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal(len(v))
}
if v, err := db.ZScan(key, nil, 10, true); err != nil {
t.Fatal(err)
} else if len(v) != 3 {
t.Fatal(len(v))
}
}
func TestZSetPersist(t *testing.T) { func TestZSetPersist(t *testing.T) {
db := getTestDB() db := getTestDB()
@ -280,6 +253,9 @@ func TestZUnionStore(t *testing.T) {
weights := []int64{1, 2} weights := []int64{1, 2}
out := []byte("out") out := []byte("out")
db.ZAdd(out, ScorePair{3, []byte("out")})
n, err := db.ZUnionStore(out, keys, weights, AggregateSum) n, err := db.ZUnionStore(out, keys, weights, AggregateSum)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
@ -323,6 +299,15 @@ func TestZUnionStore(t *testing.T) {
if n != 3 { if n != 3 {
t.Fatal("invalid value ", v) t.Fatal("invalid value ", v)
} }
n, err = db.ZCard(out)
if err != nil {
t.Fatal(err.Error())
}
if n != 3 {
t.Fatal("invalid value ", n)
}
} }
func TestZInterStore(t *testing.T) { func TestZInterStore(t *testing.T) {
@ -341,6 +326,8 @@ func TestZInterStore(t *testing.T) {
weights := []int64{2, 3} weights := []int64{2, 3}
out := []byte("out") out := []byte("out")
db.ZAdd(out, ScorePair{3, []byte("out")})
n, err := db.ZInterStore(out, keys, weights, AggregateSum) n, err := db.ZInterStore(out, keys, weights, AggregateSum)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
@ -356,7 +343,6 @@ func TestZInterStore(t *testing.T) {
t.Fatal("invalid value ", v) t.Fatal("invalid value ", v)
} }
out = []byte("out")
n, err = db.ZInterStore(out, keys, weights, AggregateMin) n, err = db.ZInterStore(out, keys, weights, AggregateMin)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
@ -382,4 +368,12 @@ func TestZInterStore(t *testing.T) {
t.Fatal("invalid value ", n) t.Fatal("invalid value ", n)
} }
n, err = db.ZCard(out)
if err != nil {
t.Fatal(err.Error())
}
if n != 1 {
t.Fatal("invalid value ", n)
}
} }

View File

@ -1,17 +1,16 @@
package server package server
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/siddontang/go-bson/bson"
"github.com/siddontang/go-log/log" "github.com/siddontang/go-log/log"
"github.com/siddontang/ledisdb/ledis" "github.com/siddontang/ledisdb/ledis"
"github.com/ugorji/go/codec"
"io" "io"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"encoding/json"
"github.com/siddontang/go-bson/bson"
"github.com/ugorji/go/codec"
) )
var allowedContentTypes = map[string]struct{}{ var allowedContentTypes = map[string]struct{}{

View File

@ -344,22 +344,6 @@ func TestBitErrorParams(t *testing.T) {
t.Fatal("invalid err of %v", err) 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 //bexpire
if _, err := c.Do("bexpire", "test_bexpire"); err == nil { if _, err := c.Do("bexpire", "test_bexpire"); err == nil {
t.Fatal("invalid err of %v", err) t.Fatal("invalid err of %v", err)

283
server/cmd_set.go Normal file
View File

@ -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)
}

203
server/cmd_set_test.go Normal file
View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -1,11 +0,0 @@
// +build !windows
package store
import (
"github.com/siddontang/ledisdb/store/boltdb"
)
func init() {
Register(boltdb.Store{})
}

3
store/boltdb/const.go Normal file
View File

@ -0,0 +1,3 @@
package boltdb
const DBName = "boltdb"

View File

@ -14,7 +14,7 @@ type Store struct {
} }
func (s Store) String() string { func (s Store) String() string {
return "boltdb" return DBName
} }
func (s Store) Open(dbPath string, cfg *config.Config) (driver.IDB, error) { func (s Store) Open(dbPath string, cfg *config.Config) (driver.IDB, error) {
@ -150,3 +150,7 @@ func (db *DB) BatchPut(writes []driver.Write) error {
}) })
return err return err
} }
func init() {
driver.Register(Store{})
}

View File

@ -1,39 +0,0 @@
package store
import (
"github.com/siddontang/ledisdb/config"
"os"
"testing"
)
func newTestBoltDB() *DB {
cfg := new(config.Config)
cfg.DBName = "boltdb"
cfg.DataDir = "/tmp/testdb"
os.RemoveAll(getStorePath(cfg))
db, err := Open(cfg)
if err != nil {
println(err.Error())
panic(err)
}
return db
}
func TestBoltDB(t *testing.T) {
db := newTestBoltDB()
testStore(db, t)
db.Close()
}
func TestBoltDBTx(t *testing.T) {
db := newTestBoltDB()
testTx(db, t)
db.Close()
}

45
store/driver/store.go Normal file
View File

@ -0,0 +1,45 @@
package driver
import (
"fmt"
"github.com/siddontang/ledisdb/config"
)
type Store interface {
String() string
Open(path string, cfg *config.Config) (IDB, error)
Repair(paht string, cfg *config.Config) error
}
var dbs = map[string]Store{}
func Register(s Store) {
name := s.String()
if _, ok := dbs[name]; ok {
panic(fmt.Errorf("store %s is registered", s))
}
dbs[name] = s
}
func ListStores() []string {
s := []string{}
for k, _ := range dbs {
s = append(s, k)
}
return s
}
func GetStore(cfg *config.Config) (Store, error) {
if len(cfg.DBName) == 0 {
cfg.DBName = config.DefaultDBName
}
s, ok := dbs[cfg.DBName]
if !ok {
return nil, fmt.Errorf("store %s is not registered", cfg.DBName)
}
return s, nil
}

View File

@ -1,9 +0,0 @@
package store
import (
"github.com/siddontang/ledisdb/store/goleveldb"
)
func init() {
Register(goleveldb.Store{})
}

3
store/goleveldb/const.go Normal file
View File

@ -0,0 +1,3 @@
package goleveldb
const DBName = "goleveldb"

View File

@ -17,7 +17,7 @@ type Store struct {
} }
func (s Store) String() string { func (s Store) String() string {
return "goleveldb" return DBName
} }
type DB struct { type DB struct {
@ -136,3 +136,7 @@ func (db *DB) NewIterator() driver.IIterator {
func (db *DB) Begin() (driver.Tx, error) { func (db *DB) Begin() (driver.Tx, error) {
return nil, driver.ErrTxSupport return nil, driver.ErrTxSupport
} }
func init() {
driver.Register(Store{})
}

View File

@ -1,32 +0,0 @@
package store
import (
"github.com/siddontang/ledisdb/config"
"os"
"testing"
)
func newTestGoLevelDB() *DB {
cfg := new(config.Config)
cfg.DBName = "goleveldb"
cfg.DataDir = "/tmp/testdb"
os.RemoveAll(getStorePath(cfg))
db, err := Open(cfg)
if err != nil {
println(err.Error())
panic(err)
}
return db
}
func TestGoLevelDB(t *testing.T) {
db := newTestGoLevelDB()
testStore(db, t)
db.Close()
}

View File

@ -1,11 +0,0 @@
// +build hyperleveldb
package store
import (
"github.com/siddontang/ledisdb/store/hyperleveldb"
)
func init() {
Register(hyperleveldb.Store{})
}

View File

@ -0,0 +1,3 @@
package hyperleveldb
const DBName = "hyperleveldb"

View File

@ -24,7 +24,7 @@ type Store struct {
} }
func (s Store) String() string { func (s Store) String() string {
return "hyperleveldb" return DBName
} }
func (s Store) Open(path string, cfg *config.Config) (driver.IDB, error) { func (s Store) Open(path string, cfg *config.Config) (driver.IDB, error) {
@ -257,3 +257,7 @@ func (db *DB) delete(wo *WriteOptions, key []byte) error {
func (db *DB) Begin() (driver.Tx, error) { func (db *DB) Begin() (driver.Tx, error) {
return nil, driver.ErrTxSupport return nil, driver.ErrTxSupport
} }
func init() {
driver.Register(Store{})
}

View File

@ -1,33 +0,0 @@
// +build hyperleveldb
package store
import (
"github.com/siddontang/ledisdb/config"
"os"
"testing"
)
func newTestHyperLevelDB() *DB {
cfg := new(config.Config)
cfg.DBName = "hyperleveldb"
cfg.DataDir = "/tmp/testdb"
os.RemoveAll(getStorePath(cfg))
db, err := Open(cfg)
if err != nil {
println(err.Error())
panic(err)
}
return db
}
func TestHyperLevelDB(t *testing.T) {
db := newTestHyperLevelDB()
testStore(db, t)
db.Close()
}

View File

@ -1,11 +0,0 @@
// +build leveldb
package store
import (
"github.com/siddontang/ledisdb/store/leveldb"
)
func init() {
Register(leveldb.Store{})
}

3
store/leveldb/const.go Normal file
View File

@ -0,0 +1,3 @@
package leveldb
const DBName = "leveldb"

View File

@ -24,7 +24,7 @@ type Store struct {
} }
func (s Store) String() string { func (s Store) String() string {
return "leveldb" return DBName
} }
func (s Store) Open(path string, cfg *config.Config) (driver.IDB, error) { func (s Store) Open(path string, cfg *config.Config) (driver.IDB, error) {
@ -257,3 +257,7 @@ func (db *DB) delete(wo *WriteOptions, key []byte) error {
func (db *DB) Begin() (driver.Tx, error) { func (db *DB) Begin() (driver.Tx, error) {
return nil, driver.ErrTxSupport return nil, driver.ErrTxSupport
} }
func init() {
driver.Register(Store{})
}

View File

@ -1,34 +0,0 @@
// +build leveldb
package store
import (
"github.com/siddontang/ledisdb/config"
"os"
"testing"
)
func newTestLevelDB() *DB {
cfg := new(config.Config)
cfg.DBName = "leveldb"
cfg.DataDir = "/tmp/testdb"
os.RemoveAll(getStorePath(cfg))
db, err := Open(cfg)
if err != nil {
println(err.Error())
panic(err)
}
return db
}
func TestLevelDB(t *testing.T) {
db := newTestLevelDB()
testStore(db, t)
db.Close()
}

View File

@ -1,10 +0,0 @@
// +build !windows
package store
import (
"github.com/siddontang/ledisdb/store/mdb"
)
func init() {
Register(mdb.Store{})
}

3
store/mdb/const.go Normal file
View File

@ -0,0 +1,3 @@
package mdb
const DBName = "lmdb"

View File

@ -1,3 +1,5 @@
// +build !windows
package mdb package mdb
import ( import (
@ -11,7 +13,7 @@ type Store struct {
} }
func (s Store) String() string { func (s Store) String() string {
return "lmdb" return DBName
} }
type MDB struct { type MDB struct {
@ -275,3 +277,7 @@ func (db MDB) NewWriteBatch() driver.IWriteBatch {
func (db MDB) Begin() (driver.Tx, error) { func (db MDB) Begin() (driver.Tx, error) {
return newTx(db) return newTx(db)
} }
func init() {
driver.Register(Store{})
}

View File

@ -1,3 +1,5 @@
// +build !windows
package mdb package mdb
import ( import (

View File

@ -1,43 +0,0 @@
// +build !windows
package store
import (
"github.com/siddontang/ledisdb/config"
"os"
"testing"
)
func newTestLMDB() *DB {
cfg := new(config.Config)
cfg.DBName = "lmdb"
cfg.DataDir = "/tmp/testdb"
cfg.LMDB.MapSize = 10 * 1024 * 1024
os.RemoveAll(getStorePath(cfg))
db, err := Open(cfg)
if err != nil {
println(err.Error())
panic(err)
}
return db
}
func TestLMDB(t *testing.T) {
db := newTestLMDB()
testStore(db, t)
db.Close()
}
func TestLMDBTx(t *testing.T) {
db := newTestLMDB()
testTx(db, t)
db.Close()
}

View File

@ -1,11 +0,0 @@
// +build rocksdb
package store
import (
"github.com/siddontang/ledisdb/store/rocksdb"
)
func init() {
Register(rocksdb.Store{})
}

3
store/rocksdb/const.go Normal file
View File

@ -0,0 +1,3 @@
package rocksdb
const DBName = "rocksdb"

View File

@ -25,7 +25,7 @@ type Store struct {
} }
func (s Store) String() string { func (s Store) String() string {
return "rocksdb" return DBName
} }
func (s Store) Open(path string, cfg *config.Config) (driver.IDB, error) { func (s Store) Open(path string, cfg *config.Config) (driver.IDB, error) {
@ -274,3 +274,7 @@ func (db *DB) delete(wo *WriteOptions, key []byte) error {
func (db *DB) Begin() (driver.Tx, error) { func (db *DB) Begin() (driver.Tx, error) {
return nil, driver.ErrTxSupport return nil, driver.ErrTxSupport
} }
func init() {
driver.Register(Store{})
}

View File

@ -1,34 +0,0 @@
// +build rocksdb
package store
import (
"github.com/siddontang/ledisdb/config"
"os"
"testing"
)
func newTestRocksDB() *DB {
cfg := new(config.Config)
cfg.DBName = "rocksdb"
cfg.DataDir = "/tmp/testdb"
os.RemoveAll(getStorePath(cfg))
db, err := Open(cfg)
if err != nil {
println(err.Error())
panic(err)
}
return db
}
func TestRocksDB(t *testing.T) {
db := newTestRocksDB()
testStore(db, t)
db.Close()
}

View File

@ -6,55 +6,21 @@ import (
"github.com/siddontang/ledisdb/store/driver" "github.com/siddontang/ledisdb/store/driver"
"os" "os"
"path" "path"
"github.com/siddontang/ledisdb/store/boltdb"
"github.com/siddontang/ledisdb/store/goleveldb"
"github.com/siddontang/ledisdb/store/hyperleveldb"
"github.com/siddontang/ledisdb/store/leveldb"
"github.com/siddontang/ledisdb/store/mdb"
"github.com/siddontang/ledisdb/store/rocksdb"
) )
type Config config.Config
type Store interface {
String() string
Open(path string, cfg *config.Config) (driver.IDB, error)
Repair(paht string, cfg *config.Config) error
}
var dbs = map[string]Store{}
func Register(s Store) {
name := s.String()
if _, ok := dbs[name]; ok {
panic(fmt.Errorf("store %s is registered", s))
}
dbs[name] = s
}
func ListStores() []string {
s := []string{}
for k, _ := range dbs {
s = append(s, k)
}
return s
}
func getStore(cfg *config.Config) (Store, error) {
if len(cfg.DBName) == 0 {
cfg.DBName = config.DefaultDBName
}
s, ok := dbs[cfg.DBName]
if !ok {
return nil, fmt.Errorf("store %s is not registered", cfg.DBName)
}
return s, nil
}
func getStorePath(cfg *config.Config) string { func getStorePath(cfg *config.Config) string {
return path.Join(cfg.DataDir, fmt.Sprintf("%s_data", cfg.DBName)) return path.Join(cfg.DataDir, fmt.Sprintf("%s_data", cfg.DBName))
} }
func Open(cfg *config.Config) (*DB, error) { func Open(cfg *config.Config) (*DB, error) {
s, err := getStore(cfg) s, err := driver.GetStore(cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -76,7 +42,7 @@ func Open(cfg *config.Config) (*DB, error) {
} }
func Repair(cfg *config.Config) error { func Repair(cfg *config.Config) error {
s, err := getStore(cfg) s, err := driver.GetStore(cfg)
if err != nil { if err != nil {
return err return err
} }
@ -85,3 +51,12 @@ func Repair(cfg *config.Config) error {
return s.Repair(path, cfg) return s.Repair(path, cfg)
} }
func init() {
_ = boltdb.DBName
_ = goleveldb.DBName
_ = hyperleveldb.DBName
_ = leveldb.DBName
_ = mdb.DBName
_ = rocksdb.DBName
}

View File

@ -3,11 +3,34 @@ package store
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/siddontang/ledisdb/config"
"github.com/siddontang/ledisdb/store/driver"
"os"
"testing" "testing"
) )
func TestStore(t *testing.T) { func TestStore(t *testing.T) {
cfg := new(config.Config)
cfg.DataDir = "/tmp/testdb"
cfg.LMDB.MapSize = 10 * 1024 * 1024
ns := driver.ListStores()
for _, s := range ns {
cfg.DBName = s
os.RemoveAll(getStorePath(cfg))
db, err := Open(cfg)
if err != nil {
t.Fatal(err)
}
testStore(db, t)
testClear(db, t)
testTx(db, t)
db.Close()
}
} }
func testStore(db *DB, t *testing.T) { func testStore(db *DB, t *testing.T) {
@ -16,6 +39,13 @@ func testStore(db *DB, t *testing.T) {
testIterator(db, t) testIterator(db, t)
} }
func testClear(db *DB, t *testing.T) {
it := db.RangeIterator(nil, nil, RangeClose)
for ; it.Valid(); it.Next() {
db.Delete(it.RawKey())
}
}
func testSimple(db *DB, t *testing.T) { func testSimple(db *DB, t *testing.T) {
key := []byte("key") key := []byte("key")
value := []byte("hello world") value := []byte("hello world")

View File

@ -1,6 +1,7 @@
package store package store
import ( import (
"github.com/siddontang/ledisdb/store/driver"
"testing" "testing"
) )
@ -9,6 +10,16 @@ func TestTx(t *testing.T) {
} }
func testTx(db *DB, t *testing.T) { func testTx(db *DB, t *testing.T) {
if tx, err := db.Begin(); err != nil {
if err == driver.ErrTxSupport {
return
} else {
t.Fatal(err)
}
} else {
tx.Rollback()
}
key1 := []byte("1") key1 := []byte("1")
key2 := []byte("2") key2 := []byte("2")
key3 := []byte("3") key3 := []byte("3")