mirror of https://github.com/ledisdb/ledisdb.git
Merge branch 'develop'
This commit is contained in:
commit
a50c84ddc4
|
@ -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.
|
||||||
|
|
|
@ -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):
|
||||||
""
|
""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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()
|
|
@ -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()
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"},
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
592
doc/commands.md
592
doc/commands.md
|
@ -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 key’s time to live in seconds, like expire similarly.
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
int64:
|
||||||
|
|
||||||
|
- 1 if the timeout was set
|
||||||
|
- 0 if key does not exist or the timeout could not be set
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> SADD key 1 2
|
||||||
|
(integer) 2
|
||||||
|
ledis> SEXPIRE key 100
|
||||||
|
(integer) 1
|
||||||
|
ledis> STTL key
|
||||||
|
(integer) 95
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### SEXPIREAT key timestamp
|
||||||
|
|
||||||
|
Sets the expiration for a set key as a unix timestamp, like expireat similarly.
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
int64:
|
||||||
|
|
||||||
|
- 1 if the timeout was set
|
||||||
|
- 0 if key does not exist or the timeout could not be set
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> SADD key 1 2
|
||||||
|
(integer) 2
|
||||||
|
ledis> SEXPIREAT key 1408094999
|
||||||
|
(integer) 1
|
||||||
|
ledis> STTL key
|
||||||
|
(integer) 908
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### STTL key
|
||||||
|
|
||||||
|
Returns the remaining time to live of a key that has a timeout. If the key was not set a timeout, -1 returns.
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
int64: TTL in seconds
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> SADD key 1 2
|
||||||
|
(integer) 2
|
||||||
|
ledis> SEXPIREAT key 1408094999
|
||||||
|
(integer) 1
|
||||||
|
ledis> STTL key
|
||||||
|
(integer) 908
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### SPERSIST key
|
||||||
|
Remove the expiration from a set key, like persist similarly. Remove the existing timeout on key.
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
int64:
|
||||||
|
|
||||||
|
- 1 if the timeout was removed
|
||||||
|
- 0 if key does not exist or does not have an timeout
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> SEXPIREAT key 1408094999
|
||||||
|
(integer) 1
|
||||||
|
ledis> STTL key
|
||||||
|
(integer) 908
|
||||||
|
ledis> SPERSIST key
|
||||||
|
(integer) 1
|
||||||
|
ledis> STTL key
|
||||||
|
(integer) -1
|
||||||
|
```
|
||||||
|
|
||||||
## ZSet
|
## 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
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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]))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{}{
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/siddontang/ledisdb/store/boltdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Register(boltdb.Store{})
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package boltdb
|
||||||
|
|
||||||
|
const DBName = "boltdb"
|
|
@ -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{})
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/siddontang/ledisdb/store/goleveldb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Register(goleveldb.Store{})
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package goleveldb
|
||||||
|
|
||||||
|
const DBName = "goleveldb"
|
|
@ -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{})
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
// +build hyperleveldb
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/siddontang/ledisdb/store/hyperleveldb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Register(hyperleveldb.Store{})
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package hyperleveldb
|
||||||
|
|
||||||
|
const DBName = "hyperleveldb"
|
|
@ -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{})
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
// +build leveldb
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/siddontang/ledisdb/store/leveldb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Register(leveldb.Store{})
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package leveldb
|
||||||
|
|
||||||
|
const DBName = "leveldb"
|
|
@ -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{})
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
10
store/mdb.go
10
store/mdb.go
|
@ -1,10 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/siddontang/ledisdb/store/mdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Register(mdb.Store{})
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package mdb
|
||||||
|
|
||||||
|
const DBName = "lmdb"
|
|
@ -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{})
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package mdb
|
package mdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
// +build rocksdb
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/siddontang/ledisdb/store/rocksdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Register(rocksdb.Store{})
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package rocksdb
|
||||||
|
|
||||||
|
const DBName = "rocksdb"
|
|
@ -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{})
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue