forked from mirror/ledisdb
Merge branch 'develop'
This commit is contained in:
commit
0011bd07f1
|
@ -1,3 +1,4 @@
|
||||||
build
|
build
|
||||||
*.pyc
|
*.pyc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
nohup.out
|
|
@ -0,0 +1,11 @@
|
||||||
|
all: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
go install ./...
|
||||||
|
|
||||||
|
clean:
|
||||||
|
go clean -i ./...
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test ./...
|
||||||
|
go test -race ./...
|
36
README.md
36
README.md
|
@ -9,7 +9,7 @@ Ledisdb is a high performance NoSQL like Redis based on LevelDB written by go. I
|
||||||
+ Supports expiration and ttl.
|
+ Supports expiration and ttl.
|
||||||
+ Redis clients, like redis-cli, are supported directly.
|
+ Redis clients, like redis-cli, are supported directly.
|
||||||
+ Multi client API supports, including Golang, Python, Lua(Openresty).
|
+ Multi client API supports, including Golang, Python, Lua(Openresty).
|
||||||
+ Easily to embed in Golang application.
|
+ Easy to embed in Golang application.
|
||||||
+ Replication to guarantee data safe.
|
+ Replication to guarantee data safe.
|
||||||
+ Supplies tools to load, dump, repair database.
|
+ Supplies tools to load, dump, repair database.
|
||||||
|
|
||||||
|
@ -25,13 +25,15 @@ Ledisdb is a high performance NoSQL like Redis based on LevelDB written by go. I
|
||||||
|
|
||||||
+ Install leveldb and snappy, if you have installed, skip.
|
+ Install leveldb and snappy, if you have installed, skip.
|
||||||
|
|
||||||
I supply a simple shell to install leveldb and snappy, you can use:
|
LedisDB supplies a simple shell to install leveldb and snappy:
|
||||||
|
|
||||||
sh build_leveldb.sh
|
sh build_leveldb.sh
|
||||||
|
|
||||||
It will default install leveldb at /usr/local/leveldb and snappy at /usr/local/snappy
|
It will default install leveldb at /usr/local/leveldb and snappy at /usr/local/snappy.
|
||||||
|
|
||||||
+ Change LEVELDB_DIR and SNAPPY_DIR to real install path in dev.sh.
|
LedisDB use the modified LevelDB for better performance, see [here](https://github.com/siddontang/ledisdb/wiki/leveldb-source-modification).
|
||||||
|
|
||||||
|
+ Set LEVELDB_DIR and SNAPPY_DIR to the actual install path in dev.sh.
|
||||||
|
|
||||||
+ Then:
|
+ Then:
|
||||||
|
|
||||||
|
@ -40,7 +42,7 @@ Ledisdb is a high performance NoSQL like Redis based on LevelDB written by go. I
|
||||||
|
|
||||||
go install ./...
|
go install ./...
|
||||||
|
|
||||||
## Run
|
## Server Example
|
||||||
|
|
||||||
./ledis-server -config=/etc/ledis.json
|
./ledis-server -config=/etc/ledis.json
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ Ledisdb is a high performance NoSQL like Redis based on LevelDB written by go. I
|
||||||
ledis 127.0.0.1:6380> get a
|
ledis 127.0.0.1:6380> get a
|
||||||
"1"
|
"1"
|
||||||
|
|
||||||
## Lib
|
## Package Example
|
||||||
|
|
||||||
import "github.com/siddontang/ledisdb/ledis"
|
import "github.com/siddontang/ledisdb/ledis"
|
||||||
l, _ := ledis.Open(cfg)
|
l, _ := ledis.Open(cfg)
|
||||||
|
@ -63,9 +65,9 @@ Ledisdb is a high performance NoSQL like Redis based on LevelDB written by go. I
|
||||||
db.Get(key)
|
db.Get(key)
|
||||||
|
|
||||||
|
|
||||||
## Replication
|
## Replication Example
|
||||||
|
|
||||||
set slaveof in config or dynamiclly
|
Set slaveof in config or dynamiclly
|
||||||
|
|
||||||
ledis-cli -p 6381
|
ledis-cli -p 6381
|
||||||
|
|
||||||
|
@ -78,15 +80,16 @@ See benchmark.md for more.
|
||||||
|
|
||||||
## Todo
|
## Todo
|
||||||
|
|
||||||
+ Admin
|
See [Issues todo](https://github.com/siddontang/ledisdb/issues?labels=todo&page=1&state=open)
|
||||||
|
|
||||||
## GoDoc
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/siddontang/ledisdb?status.png)](https://godoc.org/github.com/siddontang/ledisdb)
|
## Links
|
||||||
|
|
||||||
## Commands
|
+ [Official Website](http://ledisdb.com)
|
||||||
|
+ [Author's Chinese Blog](http://blog.csdn.net/siddontang/article/category/2264003)
|
||||||
|
+ [GoDoc](https://godoc.org/github.com/siddontang/ledisdb)
|
||||||
|
+ [Server Commands](https://github.com/siddontang/ledisdb/wiki/Commands)
|
||||||
|
|
||||||
Some server commands explaintions are [here](https://github.com/siddontang/ledisdb/wiki/Commands), others will add continuate.
|
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
|
@ -94,6 +97,13 @@ Gmail: cenqichao@gmail.com
|
||||||
|
|
||||||
Gmail: chendahui007@gmail.com
|
Gmail: chendahui007@gmail.com
|
||||||
|
|
||||||
|
Gmail: cppgohan@gmail.com
|
||||||
|
|
||||||
|
Gmail: tiaotiaoyly@gmail.com
|
||||||
|
|
||||||
|
Gmail: wyk4true@gmail.com
|
||||||
|
|
||||||
|
|
||||||
## Feedback
|
## Feedback
|
||||||
|
|
||||||
Gmail: siddontang@gmail.com
|
Gmail: siddontang@gmail.com
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import with_statement
|
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, iterkeys, itervalues,
|
from ledis._compat import (b, izip, imap, iteritems,
|
||||||
basestring, long, nativestr, urlparse, bytes)
|
basestring, long, nativestr, urlparse, bytes)
|
||||||
from ledis.connection import ConnectionPool, UnixDomainSocketConnection
|
from ledis.connection import ConnectionPool, UnixDomainSocketConnection
|
||||||
from ledis.exceptions import (
|
from ledis.exceptions import (
|
||||||
|
@ -53,9 +53,8 @@ def zset_score_pairs(response, **options):
|
||||||
"""
|
"""
|
||||||
if not response or not options['withscores']:
|
if not response or not options['withscores']:
|
||||||
return response
|
return response
|
||||||
score_cast_func = options.get('score_cast_func', int)
|
|
||||||
it = iter(response)
|
it = iter(response)
|
||||||
return list(izip(it, imap(score_cast_func, it)))
|
return list(izip(it, imap(int, it)))
|
||||||
|
|
||||||
|
|
||||||
def int_or_none(response):
|
def int_or_none(response):
|
||||||
|
@ -77,13 +76,14 @@ 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 EXPIRE EXPIREAT HEXISTS HMSET SETNX '
|
||||||
'PERSIST HPERSIST LPERSIST ZPERSIST',
|
'PERSIST HPERSIST LPERSIST ZPERSIST BEXPIRE '
|
||||||
|
'BEXPIREAT BPERSIST BDELETE',
|
||||||
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 ZREMRANGEBYRANK ZREMRANGEBYSCORE'
|
'ZADD ZCARD ZREM ZREMRANGEBYRANK ZREMRANGEBYSCORE'
|
||||||
'LMCLEAR HMCLEAR ZMCLEAR',
|
'LMCLEAR HMCLEAR ZMCLEAR BCOUNT BGETBIT BSETBIT BOPT BMSETBIT',
|
||||||
int
|
int
|
||||||
),
|
),
|
||||||
string_keys_to_dict(
|
string_keys_to_dict(
|
||||||
|
@ -102,7 +102,9 @@ class Ledis(object):
|
||||||
{
|
{
|
||||||
'HGETALL': lambda r: r and pairs_to_dict(r) or {},
|
'HGETALL': lambda r: r and pairs_to_dict(r) or {},
|
||||||
'PING': lambda r: nativestr(r) == 'PONG',
|
'PING': lambda r: nativestr(r) == 'PONG',
|
||||||
'SET': lambda r: r and nativestr(r) == 'OK', }
|
'SET': lambda r: r and nativestr(r) == 'OK',
|
||||||
|
},
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -187,7 +189,6 @@ class Ledis(object):
|
||||||
return self.response_callbacks[command_name](response, **options)
|
return self.response_callbacks[command_name](response, **options)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
#### SERVER INFORMATION ####
|
#### SERVER INFORMATION ####
|
||||||
def echo(self, value):
|
def echo(self, value):
|
||||||
"Echo the string back from the server"
|
"Echo the string back from the server"
|
||||||
|
@ -205,7 +206,6 @@ class Ledis(object):
|
||||||
db = 0
|
db = 0
|
||||||
return self.execute_command('SELECT', db)
|
return self.execute_command('SELECT', db)
|
||||||
|
|
||||||
|
|
||||||
#### BASIC KEY COMMANDS ####
|
#### BASIC KEY COMMANDS ####
|
||||||
def decr(self, name, amount=1):
|
def decr(self, name, amount=1):
|
||||||
"""
|
"""
|
||||||
|
@ -327,7 +327,6 @@ class Ledis(object):
|
||||||
"Removes an expiration on name"
|
"Removes an expiration on name"
|
||||||
return self.execute_command('PERSIST', name)
|
return self.execute_command('PERSIST', name)
|
||||||
|
|
||||||
|
|
||||||
#### LIST COMMANDS ####
|
#### LIST COMMANDS ####
|
||||||
def lindex(self, name, index):
|
def lindex(self, name, index):
|
||||||
"""
|
"""
|
||||||
|
@ -454,17 +453,15 @@ class Ledis(object):
|
||||||
|
|
||||||
``withscores`` indicates to return the scores along with the values.
|
``withscores`` indicates to return the scores along with the values.
|
||||||
The return type is a list of (value, score) pairs
|
The return type is a list of (value, score) pairs
|
||||||
|
|
||||||
``score_cast_func`` a callable used to cast the score return value
|
|
||||||
"""
|
"""
|
||||||
if desc:
|
if desc:
|
||||||
return self.zrevrange(name, start, end, withscores,
|
return self.zrevrange(name, start, end, withscores)
|
||||||
score_cast_func)
|
|
||||||
pieces = ['ZRANGE', name, start, end]
|
pieces = ['ZRANGE', name, start, end]
|
||||||
if withscores:
|
if withscores:
|
||||||
pieces.append('withscores')
|
pieces.append('withscores')
|
||||||
options = {
|
options = {
|
||||||
'withscores': withscores, 'score_cast_func': int}
|
'withscores': withscores}
|
||||||
return self.execute_command(*pieces, **options)
|
return self.execute_command(*pieces, **options)
|
||||||
|
|
||||||
def zrangebyscore(self, name, min, max, start=None, num=None,
|
def zrangebyscore(self, name, min, max, start=None, num=None,
|
||||||
|
@ -478,8 +475,6 @@ class Ledis(object):
|
||||||
|
|
||||||
``withscores`` indicates to return the scores along with the values.
|
``withscores`` indicates to return the scores along with the values.
|
||||||
The return type is a list of (value, score) pairs
|
The return type is a list of (value, score) pairs
|
||||||
|
|
||||||
`score_cast_func`` a callable used to cast the score return value
|
|
||||||
"""
|
"""
|
||||||
if (start is not None and num is None) or \
|
if (start is not None and num is None) or \
|
||||||
(num is not None and start is None):
|
(num is not None and start is None):
|
||||||
|
@ -490,7 +485,7 @@ class Ledis(object):
|
||||||
if withscores:
|
if withscores:
|
||||||
pieces.append('withscores')
|
pieces.append('withscores')
|
||||||
options = {
|
options = {
|
||||||
'withscores': withscores, 'score_cast_func': int}
|
'withscores': withscores}
|
||||||
return self.execute_command(*pieces, **options)
|
return self.execute_command(*pieces, **options)
|
||||||
|
|
||||||
def zrank(self, name, value):
|
def zrank(self, name, value):
|
||||||
|
@ -529,14 +524,11 @@ class Ledis(object):
|
||||||
|
|
||||||
``withscores`` indicates to return the scores along with the values
|
``withscores`` indicates to return the scores along with the values
|
||||||
The return type is a list of (value, score) pairs
|
The return type is a list of (value, score) pairs
|
||||||
|
|
||||||
``score_cast_func`` a callable used to cast the score return value
|
|
||||||
"""
|
"""
|
||||||
pieces = ['ZREVRANGE', name, start, num]
|
pieces = ['ZREVRANGE', name, start, num]
|
||||||
if withscores:
|
if withscores:
|
||||||
pieces.append('withscores')
|
pieces.append('withscores')
|
||||||
options = {
|
options = {'withscores': withscores}
|
||||||
'withscores': withscores, 'score_cast_func': int}
|
|
||||||
return self.execute_command(*pieces, **options)
|
return self.execute_command(*pieces, **options)
|
||||||
|
|
||||||
def zrevrangebyscore(self, name, min, max, start=None, num=None,
|
def zrevrangebyscore(self, name, min, max, start=None, num=None,
|
||||||
|
@ -550,8 +542,6 @@ class Ledis(object):
|
||||||
|
|
||||||
``withscores`` indicates to return the scores along with the values.
|
``withscores`` indicates to return the scores along with the values.
|
||||||
The return type is a list of (value, score) pairs
|
The return type is a list of (value, score) pairs
|
||||||
|
|
||||||
``score_cast_func`` a callable used to cast the score return value
|
|
||||||
"""
|
"""
|
||||||
if (start is not None and num is None) or \
|
if (start is not None and num is None) or \
|
||||||
(num is not None and start is None):
|
(num is not None and start is None):
|
||||||
|
@ -561,8 +551,7 @@ class Ledis(object):
|
||||||
pieces.extend(['LIMIT', start, num])
|
pieces.extend(['LIMIT', start, num])
|
||||||
if withscores:
|
if withscores:
|
||||||
pieces.append('withscores')
|
pieces.append('withscores')
|
||||||
options = {
|
options = {'withscores': withscores}
|
||||||
'withscores': withscores, 'score_cast_func': int}
|
|
||||||
return self.execute_command(*pieces, **options)
|
return self.execute_command(*pieces, **options)
|
||||||
|
|
||||||
def zrevrank(self, name, value):
|
def zrevrank(self, name, value):
|
||||||
|
@ -703,3 +692,79 @@ class Ledis(object):
|
||||||
"Removes an expiration on name"
|
"Removes an expiration on name"
|
||||||
return self.execute_command('HPERSIST', name)
|
return self.execute_command('HPERSIST', name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### BIT COMMANDS
|
||||||
|
def bget(self, name):
|
||||||
|
""
|
||||||
|
return self.execute_command("BGET", name)
|
||||||
|
|
||||||
|
def bdelete(self, name):
|
||||||
|
""
|
||||||
|
return self.execute_command("BDELETE", name)
|
||||||
|
|
||||||
|
def bsetbit(self, name, offset, value):
|
||||||
|
""
|
||||||
|
value = value and 1 or 0
|
||||||
|
return self.execute_command("BSETBIT", name, offset, value)
|
||||||
|
|
||||||
|
def bgetbit(self, name, offset):
|
||||||
|
""
|
||||||
|
return self.execute_command("BGETBIT", name, offset)
|
||||||
|
|
||||||
|
def bmsetbit(self, name, *args):
|
||||||
|
"""
|
||||||
|
Set any number of offset, value pairs to the key ``name``. Pairs can be
|
||||||
|
specified in the following way:
|
||||||
|
|
||||||
|
offset1, value1, offset2, value2, ...
|
||||||
|
"""
|
||||||
|
pieces = []
|
||||||
|
if args:
|
||||||
|
if len(args) % 2 != 0:
|
||||||
|
raise LedisError("BMSETBIT requires an equal number of "
|
||||||
|
"offset and value")
|
||||||
|
pieces.extend(args)
|
||||||
|
return self.execute_command("BMSETBIT", name, *pieces)
|
||||||
|
|
||||||
|
def bcount(self, key, start=None, end=None):
|
||||||
|
""
|
||||||
|
params = [key]
|
||||||
|
if start is not None and end is not None:
|
||||||
|
params.append(start)
|
||||||
|
params.append(end)
|
||||||
|
elif (start is not None and end is None) or \
|
||||||
|
(start is None and end is not None):
|
||||||
|
raise LedisError("Both start and end must be specified")
|
||||||
|
return self.execute_command("BCOUNT", *params)
|
||||||
|
|
||||||
|
def bopt(self, operation, dest, *keys):
|
||||||
|
"""
|
||||||
|
Perform a bitwise operation using ``operation`` between ``keys`` and
|
||||||
|
store the result in ``dest``.
|
||||||
|
``operation`` is one of `and`, `or`, `xor`, `not`.
|
||||||
|
"""
|
||||||
|
return self.execute_command('BOPT', operation, dest, *keys)
|
||||||
|
|
||||||
|
def bexpire(self, name, time):
|
||||||
|
"Set timeout on key ``name`` with ``time``"
|
||||||
|
if isinstance(time, datetime.timedelta):
|
||||||
|
time = time.seconds + time.days * 24 * 3600
|
||||||
|
return self.execute_command('BEXPIRE', name, time)
|
||||||
|
|
||||||
|
def bexpireat(self, name, when):
|
||||||
|
"""
|
||||||
|
Set an expire flag on key name for time seconds. time can be represented by
|
||||||
|
an integer or a Python timedelta object.
|
||||||
|
"""
|
||||||
|
if isinstance(when, datetime.datetime):
|
||||||
|
when = int(mod_time.mktime(when.timetuple()))
|
||||||
|
return self.execute_command('BEXPIREAT', name, when)
|
||||||
|
|
||||||
|
def bttl(self, name):
|
||||||
|
"Returns the number of seconds until the key name will expire"
|
||||||
|
return self.execute_command('BTTL', name)
|
||||||
|
|
||||||
|
def bpersist(self, name):
|
||||||
|
"Removes an expiration on name"
|
||||||
|
return self.execute_command('BPERSIST', name)
|
|
@ -0,0 +1,118 @@
|
||||||
|
# coding: utf-8
|
||||||
|
# Test Cases for bit commands
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import sys
|
||||||
|
import datetime, time
|
||||||
|
sys.path.append('..')
|
||||||
|
|
||||||
|
import ledis
|
||||||
|
from ledis._compat import b
|
||||||
|
from ledis import ResponseError
|
||||||
|
|
||||||
|
|
||||||
|
l = ledis.Ledis(port=6380)
|
||||||
|
|
||||||
|
|
||||||
|
def current_time():
|
||||||
|
return datetime.datetime.now()
|
||||||
|
|
||||||
|
|
||||||
|
class TestCmdBit(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
l.bdelete('a')
|
||||||
|
l.bdelete('non_exists_key')
|
||||||
|
|
||||||
|
def test_bget(self):
|
||||||
|
"bget is the same as get in K/V commands"
|
||||||
|
l.bmsetbit('a', 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 0)
|
||||||
|
assert l.bget('a') == b('\x7f')
|
||||||
|
|
||||||
|
def test_bdelete(self):
|
||||||
|
l.bsetbit('a', 0, 1)
|
||||||
|
assert l.bdelete('a')
|
||||||
|
assert not l.bdelete('non_exists_key')
|
||||||
|
|
||||||
|
def test_get_set_bit(self):
|
||||||
|
assert not l.bgetbit('a', 5)
|
||||||
|
assert not l.bsetbit('a', 5, True)
|
||||||
|
assert l.bgetbit('a', 5)
|
||||||
|
|
||||||
|
assert not l.bsetbit('a', 4, False)
|
||||||
|
assert not l.bgetbit('a', 4)
|
||||||
|
|
||||||
|
assert not l.bsetbit('a', 4, True)
|
||||||
|
assert l.bgetbit('a', 4)
|
||||||
|
|
||||||
|
assert l.bsetbit('a', 5, True)
|
||||||
|
assert l.bgetbit('a', 5)
|
||||||
|
|
||||||
|
def test_bmsetbit(self):
|
||||||
|
assert l.bmsetbit('a', 0, 1, 2, 1, 3, 1) == 3
|
||||||
|
|
||||||
|
def test_bcount(self):
|
||||||
|
l.bsetbit('a', 5, 1)
|
||||||
|
assert l.bcount('a') == 1
|
||||||
|
l.bsetbit('a', 6, 1)
|
||||||
|
assert l.bcount('a') == 2
|
||||||
|
l.bsetbit('a', 5, 0)
|
||||||
|
assert l.bcount('a') == 1
|
||||||
|
l.bmsetbit('a', 10, 1, 20, 1, 30, 1, 40, 1)
|
||||||
|
assert l.bcount('a') == 5
|
||||||
|
assert l.bcount('a', 0, 10) == 2
|
||||||
|
assert l.bcount('a', 20, 30) == 2
|
||||||
|
assert l.bcount('a', 10, 10) == 1
|
||||||
|
|
||||||
|
def test_bopt_not_empty_string(self):
|
||||||
|
l.bopt('not', 'r', 'a')
|
||||||
|
assert l.bget('r') is None
|
||||||
|
|
||||||
|
def test_bopt(self):
|
||||||
|
l.bmsetbit('a1', 10, 1, 30, 1, 50, 1, 70, 1, 90, 1)
|
||||||
|
l.bmsetbit('a2', 20, 1, 40, 1, 60, 1, 80, 1, 100, 1)
|
||||||
|
assert l.bopt('and', 'res1', 'a1', 'a2') == 101
|
||||||
|
assert l.bcount('res1') == 0
|
||||||
|
|
||||||
|
assert l.bopt('or', 'res2', 'a1', 'a2') == 101
|
||||||
|
assert l.bcount('res2') == 10
|
||||||
|
|
||||||
|
assert l.bopt('xor', 'res3', 'a1', 'a2') == 101
|
||||||
|
assert l.bcount('res3') == 10
|
||||||
|
|
||||||
|
assert l.bopt('not', 'res4', 'a1') == 91
|
||||||
|
assert l.bcount('res4') == 86
|
||||||
|
|
||||||
|
def test_bexpire(self):
|
||||||
|
assert not l.bexpire('a', 100)
|
||||||
|
l.bsetbit('a', 1, True)
|
||||||
|
assert l.bexpire('a', 100)
|
||||||
|
assert 0 < l.bttl('a') <= 100
|
||||||
|
assert l.bpersist('a')
|
||||||
|
assert l.bttl('a') == -1
|
||||||
|
|
||||||
|
def test_bexpireat_datetime(self):
|
||||||
|
expire_at = current_time() + datetime.timedelta(minutes=1)
|
||||||
|
l.bsetbit('a', 1, True)
|
||||||
|
assert l.bexpireat('a', expire_at)
|
||||||
|
assert 0 < l.bttl('a') <= 61
|
||||||
|
|
||||||
|
def test_bexpireat_unixtime(self):
|
||||||
|
expire_at = current_time() + datetime.timedelta(minutes=1)
|
||||||
|
l.bsetbit('a', 1, True)
|
||||||
|
expire_at_seconds = int(time.mktime(expire_at.timetuple()))
|
||||||
|
assert l.bexpireat('a', expire_at_seconds)
|
||||||
|
assert 0 < l.bttl('a') <= 61
|
||||||
|
|
||||||
|
def test_bexpireat_no_key(self):
|
||||||
|
expire_at = current_time() + datetime.timedelta(minutes=1)
|
||||||
|
assert not l.bexpireat('a', expire_at)
|
||||||
|
|
||||||
|
def test_bttl_and_bpersist(self):
|
||||||
|
l.bsetbit('a', 1, True)
|
||||||
|
l.bexpire('a', 100)
|
||||||
|
assert 0 < l.bttl('a') <= 100
|
||||||
|
assert l.bpersist('a')
|
||||||
|
assert l.bttl('a') == -1
|
|
@ -122,7 +122,7 @@ class TestCmdHash(unittest.TestCase):
|
||||||
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_zexpireat_no_key(self):
|
def test_hexpireat_no_key(self):
|
||||||
expire_at = current_time() + datetime.timedelta(minutes=1)
|
expire_at = current_time() + datetime.timedelta(minutes=1)
|
||||||
assert not l.hexpireat('a', expire_at)
|
assert not l.hexpireat('a', expire_at)
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,19 @@ local commands = {
|
||||||
"zttl",
|
"zttl",
|
||||||
"zpersist",
|
"zpersist",
|
||||||
|
|
||||||
|
--[[bit]]
|
||||||
|
"bget",
|
||||||
|
"bdelete",
|
||||||
|
"bsetbit",
|
||||||
|
"bgetbit",
|
||||||
|
"bmsetbit",
|
||||||
|
"bcount",
|
||||||
|
"bopt",
|
||||||
|
"bexpire",
|
||||||
|
"bexpireat",
|
||||||
|
"bttl",
|
||||||
|
"bpersist",
|
||||||
|
|
||||||
--[[server]]
|
--[[server]]
|
||||||
"ping",
|
"ping",
|
||||||
"echo",
|
"echo",
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
var helpCommands = [][]string{
|
||||||
|
{"DECR", "key", "KV"},
|
||||||
|
{"DECRBY", "key decrement", "KV"},
|
||||||
|
{"DEL", "key [key ...]", "KV"},
|
||||||
|
{"EXISTS", "key", "KV"},
|
||||||
|
{"GET", "key", "KV"},
|
||||||
|
{"GETSET", " key value", "KV"},
|
||||||
|
{"INCR", "key", "KV"},
|
||||||
|
{"INCRBY", "key increment", "KV"},
|
||||||
|
{"MGET", "key [key ...]", "KV"},
|
||||||
|
{"MSET", "key value [key value ...]", "KV"},
|
||||||
|
{"SET", "key value", "KV"},
|
||||||
|
{"SETNX", "key value", "KV"},
|
||||||
|
{"EXPIRE", "key seconds", "KV"},
|
||||||
|
{"EXPIREAT", "key timestamp", "KV"},
|
||||||
|
{"TTL", "PERSIST", "KV"},
|
||||||
|
{"HDEL", "key field [field ...]", "Hash"},
|
||||||
|
{"HEXISTS", "key field", "Hash"},
|
||||||
|
{"HGET", "key field", "Hash"},
|
||||||
|
{"HGETALL", "key", "Hash"},
|
||||||
|
{"HINCRBY", "key field increment", "Hash"},
|
||||||
|
{"HKEYS", "key", "Hash"},
|
||||||
|
{"HLEN", "key", "Hash"},
|
||||||
|
{"HMGET", "key field [field ...]", "Hash"},
|
||||||
|
{"HMSET", "key field value [field value ...]", "Hash"},
|
||||||
|
{"HSET", "key field value", "Hash"},
|
||||||
|
{"HVALS", "key", "Hash"},
|
||||||
|
{"HCLEAR", "key", "Hash"},
|
||||||
|
{"HMCLEAR", "key [key ...]", "Hash"},
|
||||||
|
{"HEXPIRE", "key seconds", "Hash"},
|
||||||
|
{"HEXPIREAT", "key timestamp", "Hash"},
|
||||||
|
{"HTTL", "key", "Hash"},
|
||||||
|
{"HPERSIST", "key", "Hash"},
|
||||||
|
{"LINDEX", "key index", "List"},
|
||||||
|
{"LLEN", "key", "List"},
|
||||||
|
{"LPOP", "key", "List"},
|
||||||
|
{"LPUSH", "key value [value ...]", "List"},
|
||||||
|
{"LRANGE", "key start stop", "List"},
|
||||||
|
{"RPOP", "key", "List"},
|
||||||
|
{"RPUSH", "key value [value ...]", "List"},
|
||||||
|
{"LCLEAR", "key", "List"},
|
||||||
|
{"LMCLEAR", "key [key ...]", "List"},
|
||||||
|
{"LEXPIRE", "key seconds", "List"},
|
||||||
|
{"LEXPIREAT", "key timestamp", "List"},
|
||||||
|
{"LTTL", "key", "List"},
|
||||||
|
{"LPERSIST", "key", "List"},
|
||||||
|
{"ZADD", "key score member [score member ...]", "ZSet"},
|
||||||
|
{"ZCARD", "key", "ZSet"},
|
||||||
|
{"ZCOUNT", "key min max", "ZSet"},
|
||||||
|
{"ZINCRBY", "key increment member", "ZSet"},
|
||||||
|
{"ZRANGE", "key start stop [WITHSCORES]", "ZSet"},
|
||||||
|
{"ZRANGEBYSCORE", "key min max [WITHSCORES] [LIMIT offset count]", "ZSet"},
|
||||||
|
{"ZRANK", "key member", "ZSet"},
|
||||||
|
{"ZREM", "key member [member ...]", "ZSet"},
|
||||||
|
{"ZREMRANGEBYRANK", "key start stop", "ZSet"},
|
||||||
|
{"ZREMRANGEBYSCORE", "key min max", "ZSet"},
|
||||||
|
{"ZREVRANGEBYSCORE", "key max min [WITHSCORES][LIMIT offset count]", "ZSet"},
|
||||||
|
{"ZREVRANK", "key member", "ZSet"},
|
||||||
|
{"ZSCORE", "key member", "ZSet"},
|
||||||
|
{"ZCLEAR", "key", "ZSet"},
|
||||||
|
{"ZMCLEAR", "key [key ...]", "ZSet"},
|
||||||
|
{"ZEXPIRE", "key seconds", "ZSet"},
|
||||||
|
{"ZEXPIREAT", "key timestamp", "ZSet"},
|
||||||
|
{"ZTTL", "key", "ZSet"},
|
||||||
|
{"ZPERSIST", "key", "ZSet"},
|
||||||
|
{"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"},
|
||||||
|
}
|
|
@ -5,12 +5,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/siddontang/ledisdb/client/go/ledis"
|
"github.com/siddontang/ledisdb/client/go/ledis"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ip = flag.String("h", "127.0.0.1", "ledisdb server ip (default 127.0.0.1)")
|
var ip = flag.String("h", "127.0.0.1", "ledisdb server ip (default 127.0.0.1)")
|
||||||
var port = flag.Int("p", 6380, "ledisdb server port (default 6380)")
|
var port = flag.Int("p", 6380, "ledisdb server port (default 6380)")
|
||||||
var socket = flag.String("s", "", "ledisdb server socket, overwrite ip and port")
|
var socket = flag.String("s", "", "ledisdb server socket, overwrite ip and port")
|
||||||
|
var dbn = flag.Int("n", 0, "ledisdb database number(default 0)")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
@ -30,8 +32,20 @@ func main() {
|
||||||
|
|
||||||
reg, _ := regexp.Compile(`'.*?'|".*?"|\S+`)
|
reg, _ := regexp.Compile(`'.*?'|".*?"|\S+`)
|
||||||
|
|
||||||
|
prompt := ""
|
||||||
|
|
||||||
for {
|
for {
|
||||||
cmd, err := line(fmt.Sprintf("%s> ", cfg.Addr))
|
if *dbn >= 16 {
|
||||||
|
fmt.Printf("ERR invalid db index %d. Auto switch back db 0.\n", *dbn)
|
||||||
|
*dbn = 0
|
||||||
|
continue
|
||||||
|
} else if *dbn > 0 && *dbn < 16 {
|
||||||
|
prompt = fmt.Sprintf("%s[%d]>", cfg.Addr, *dbn)
|
||||||
|
} else {
|
||||||
|
prompt = fmt.Sprintf("%s>", cfg.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := line(prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s\n", err.Error())
|
fmt.Printf("%s\n", err.Error())
|
||||||
return
|
return
|
||||||
|
@ -44,18 +58,30 @@ func main() {
|
||||||
addHistory(cmd)
|
addHistory(cmd)
|
||||||
|
|
||||||
args := make([]interface{}, len(cmds[1:]))
|
args := make([]interface{}, len(cmds[1:]))
|
||||||
|
|
||||||
for i := range args {
|
for i := range args {
|
||||||
args[i] = strings.Trim(string(cmds[1+i]), "\"'")
|
args[i] = strings.Trim(string(cmds[1+i]), "\"'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd := cmds[0]
|
||||||
|
if strings.ToLower(cmd) == "help" || cmd == "?" {
|
||||||
|
printHelp(cmds)
|
||||||
|
} else {
|
||||||
r, err := c.Do(cmds[0], args...)
|
r, err := c.Do(cmds[0], args...)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s", err.Error())
|
fmt.Printf("%s", err.Error())
|
||||||
|
} else if nb, _ := strconv.Atoi(cmds[1]); strings.ToLower(cmds[0]) == "select" && nb < 16 {
|
||||||
|
*dbn = nb
|
||||||
|
printReply(cmd, r)
|
||||||
} else {
|
} else {
|
||||||
printReply(cmd, r)
|
printReply(cmd, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +92,7 @@ func printReply(cmd string, reply interface{}) {
|
||||||
case string:
|
case string:
|
||||||
fmt.Printf("%s", reply)
|
fmt.Printf("%s", reply)
|
||||||
case []byte:
|
case []byte:
|
||||||
fmt.Printf("%s", string(reply))
|
fmt.Printf("%q", reply)
|
||||||
case nil:
|
case nil:
|
||||||
fmt.Printf("(nil)")
|
fmt.Printf("(nil)")
|
||||||
case ledis.Error:
|
case ledis.Error:
|
||||||
|
@ -87,3 +113,34 @@ func printReply(cmd string, reply interface{}) {
|
||||||
fmt.Printf("invalid ledis reply")
|
fmt.Printf("invalid ledis reply")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printGenericHelp() {
|
||||||
|
msg :=
|
||||||
|
`ledis-cli
|
||||||
|
Type: "help <command>" for help on <command>
|
||||||
|
`
|
||||||
|
fmt.Println(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printCommandHelp(arr []string) {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("\t%s %s \n", arr[0], arr[1])
|
||||||
|
fmt.Printf("\tGroup: %s \n", arr[2])
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func printHelp(cmds []string) {
|
||||||
|
args := cmds[1:]
|
||||||
|
if len(args) == 0 {
|
||||||
|
printGenericHelp()
|
||||||
|
} else if len(args) > 1 {
|
||||||
|
fmt.Println()
|
||||||
|
} else {
|
||||||
|
cmd := strings.ToUpper(args[0])
|
||||||
|
for i := 0; i < len(helpCommands); i++ {
|
||||||
|
if helpCommands[i][0] == cmd {
|
||||||
|
printCommandHelp(helpCommands[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,16 +3,13 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"github.com/siddontang/ledisdb/ledis"
|
"github.com/siddontang/ledisdb/ledis"
|
||||||
"github.com/siddontang/ledisdb/server"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var configPath = flag.String("config", "/etc/ledis.json", "ledisdb config file")
|
var configPath = flag.String("config", "/etc/ledis.json", "ledisdb config file")
|
||||||
var dumpPath = flag.String("dump_file", "", "ledisdb dump file")
|
var dumpPath = flag.String("dump_file", "", "ledisdb dump file")
|
||||||
var masterAddr = flag.String("master_addr", "",
|
|
||||||
"master addr to set where dump file comes from, if not set correctly, next slaveof may cause fullsync")
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
@ -74,13 +71,11 @@ func loadDump(cfg *ledis.Config, ldb *ledis.Ledis) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
info := new(server.MasterInfo)
|
//master enable binlog, here output this like mysql
|
||||||
|
if head.LogFileIndex != 0 && head.LogPos != 0 {
|
||||||
info.Addr = *masterAddr
|
format := "MASTER_LOG_FILE='binlog.%07d', MASTER_LOG_POS=%d;\n"
|
||||||
info.LogFileIndex = head.LogFileIndex
|
fmt.Printf(format, head.LogFileIndex, head.LogPos)
|
||||||
info.LogPos = head.LogPos
|
}
|
||||||
|
|
||||||
infoFile := path.Join(cfg.DataDir, "master.info")
|
return nil
|
||||||
|
|
||||||
return info.Save(infoFile)
|
|
||||||
}
|
}
|
||||||
|
|
197
doc/commands.md
197
doc/commands.md
|
@ -70,7 +70,7 @@ Table of Contents
|
||||||
- [ZREMRANGEBYRANK key start stop](#zremrangebyrank-key-start-stop)
|
- [ZREMRANGEBYRANK key start stop](#zremrangebyrank-key-start-stop)
|
||||||
- [ZREMRANGEBYSCORE key min max](#zremrangebyscore-key-min-max)
|
- [ZREMRANGEBYSCORE key min max](#zremrangebyscore-key-min-max)
|
||||||
- [ZREVRANGE key start stop [WITHSCORES]](#zrevrange-key-start-stop-withscores)
|
- [ZREVRANGE key start stop [WITHSCORES]](#zrevrange-key-start-stop-withscores)
|
||||||
- [ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]](#zrevrangebyscore--key-max-min-withscores-limit-offset-count)
|
- [ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]](#zrevrangebyscore-key-max-min-withscores-limit-offset-count)
|
||||||
- [ZREVRANK key member](#zrevrank-key-member)
|
- [ZREVRANK key member](#zrevrank-key-member)
|
||||||
- [ZSCORE key member](#zscore-key-member)
|
- [ZSCORE key member](#zscore-key-member)
|
||||||
- [ZCLEAR key](#zclear-key)
|
- [ZCLEAR key](#zclear-key)
|
||||||
|
@ -79,6 +79,19 @@ Table of Contents
|
||||||
- [ZEXPIREAT key timestamp](#zexpireat-key-timestamp)
|
- [ZEXPIREAT key timestamp](#zexpireat-key-timestamp)
|
||||||
- [ZTTL key](#zttl-key)
|
- [ZTTL key](#zttl-key)
|
||||||
- [ZPERSIST key](#zpersist-key)
|
- [ZPERSIST key](#zpersist-key)
|
||||||
|
- [Bitmap](#bitmap)
|
||||||
|
|
||||||
|
- [BGET key](#bget-key)
|
||||||
|
- [BGETBIT key offset](#bgetbit-key-offset)
|
||||||
|
- [BSETBIT key offset value](#bsetbit-key-offset-value)
|
||||||
|
- [BMSETBIT key offset value[offset value ...]](#bmsetbit-key-offset-value-offset-value-)
|
||||||
|
- [BOPT operation destkey key [key ...]](#bopt-operation-destkey-key-key-)
|
||||||
|
- [BCOUNT key [start, end]](#bcount-key-start-end)
|
||||||
|
- [BEXPIRE key seconds](#bexpire-key-seconds)
|
||||||
|
- [BEXPIREAT key timestamp](#bexpireat-key-timestamp)
|
||||||
|
- [BTTL key](#bttl-key)
|
||||||
|
- [BPERSIST key](#bpersist-key)
|
||||||
|
|
||||||
- [Replication](#replication)
|
- [Replication](#replication)
|
||||||
- [SLAVEOF host port](#slaveof-host-port)
|
- [SLAVEOF host port](#slaveof-host-port)
|
||||||
- [FULLSYNC](#fullsync)
|
- [FULLSYNC](#fullsync)
|
||||||
|
@ -1138,7 +1151,7 @@ ledis> ZRANGE myset 0 -1 WITHSCORES
|
||||||
6) "2"
|
6) "2"
|
||||||
7) "three"
|
7) "three"
|
||||||
8) "3"
|
8) "3"
|
||||||
ledis> zcard myset
|
ledis> ZCARD myset
|
||||||
(integer) 4
|
(integer) 4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1168,9 +1181,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 myset -inf +inf
|
||||||
(integer) 4
|
(integer) 4
|
||||||
ledis> zcount myset (1 3
|
ledis> ZCOUNT myset (1 3
|
||||||
(integer) 2
|
(integer) 2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1444,13 +1457,13 @@ Use ZRANK to get the rank of an element with the scores ordered from low to high
|
||||||
**Examples**
|
**Examples**
|
||||||
|
|
||||||
```
|
```
|
||||||
127.0.0.1:6380> zadd myset 1 one
|
ledis> ZADD myset 1 one
|
||||||
(integer) 1
|
(integer) 1
|
||||||
127.0.0.1:6380> zadd myset 2 two
|
ledis> ZADD myset 2 two
|
||||||
(integer) 1
|
(integer) 1
|
||||||
127.0.0.1:6380> zrevrank myset one
|
ledis> ZREVRANK myset one
|
||||||
(integer) 1
|
(integer) 1
|
||||||
127.0.0.1:6380> zrevrank myset three
|
ledis> ZREVRANK myset three
|
||||||
(nil)
|
(nil)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1581,13 +1594,13 @@ int64: TTL in seconds
|
||||||
**Examples**
|
**Examples**
|
||||||
|
|
||||||
```
|
```
|
||||||
ledis> zadd myset 1 'one'
|
ledis> ZADD myset 1 'one'
|
||||||
(integer) 1
|
(integer) 1
|
||||||
ledis> zexpire myset 100
|
ledis> ZEXPIRE myset 100
|
||||||
(integer) 1
|
(integer) 1
|
||||||
ledis> zttl myset
|
ledis> ZTTL myset
|
||||||
(integer) 97
|
(integer) 97
|
||||||
ledis> zttl myset2
|
ledis> ZTTL myset2
|
||||||
(integer) -1
|
(integer) -1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1617,6 +1630,166 @@ ledis> ZTTL mset
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Bitmap
|
||||||
|
|
||||||
|
|
||||||
|
### BGET key
|
||||||
|
|
||||||
|
Returns the whole binary data stored at `key`.
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
bulk: the raw value of key, or nil when key does not exist.
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> BMSETBIT flag 0 1 5 1 6 1
|
||||||
|
(integer) 3
|
||||||
|
ledis> BGET flag
|
||||||
|
a
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### BGETBIT key offset
|
||||||
|
|
||||||
|
Returns the bit value at `offset` in the string value stored at `key`.
|
||||||
|
When *offset* beyond the data length, ot the target data is not exist, the bit value will be 0 always.
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
int64 : the bit value stored at offset.
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> BSETBIT flag 1024 1
|
||||||
|
(integer) 0
|
||||||
|
ledis> BGETBIT flag 0
|
||||||
|
(integer) 0
|
||||||
|
ledis> BGETBIT flag 1024
|
||||||
|
(integer) 1
|
||||||
|
ledis> BGETBIT flag 65535
|
||||||
|
(integer) 0
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### BSETBIT key offset value
|
||||||
|
|
||||||
|
Sets or clear the bit at `offset` in the binary data sotred at `key`.
|
||||||
|
The bit is either set or cleared depending on `value`, which can be either `0` or `1`.
|
||||||
|
The *offset* argument is required to be qual to 0, and smaller than
|
||||||
|
2^23 (this means bitmap limits to 8MB).
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
int64 : the original bit value stored at offset.
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> BSETBIT flag 0 1
|
||||||
|
(integer) 0
|
||||||
|
ledis> BSETBIT flag 0 0
|
||||||
|
(integer) 1
|
||||||
|
ledis> BGETBIT flag 0 99
|
||||||
|
ERR invalid command param
|
||||||
|
```
|
||||||
|
|
||||||
|
### BMSETBIT key offset value [offset value ...]
|
||||||
|
Sets the given *offset* to their respective values.
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
int64 : The number of input *offset*
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> BMSETBIT flag 0 1 1 1 2 0 3 1
|
||||||
|
(integer) 4
|
||||||
|
ledis> BCOUNT flag
|
||||||
|
(integer) 3
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### BOPT operation destkey key [key ...]
|
||||||
|
Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination key.
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
Int64:
|
||||||
|
The size of the string stored in the destination key, that is equal to the size of the longest input string.
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> BMSETBIT a 0 1 2 1
|
||||||
|
(integer) 2
|
||||||
|
ledis> BMSETBIT b 1 1
|
||||||
|
(integer) 1
|
||||||
|
ledis> BOPT AND res a b
|
||||||
|
(integer) 3
|
||||||
|
ledis> BCOUNT res
|
||||||
|
(integer) 0
|
||||||
|
ledis> BOPT OR res2 a b
|
||||||
|
(integer) 3
|
||||||
|
ledis> BCOUNT res2
|
||||||
|
(integer) 3
|
||||||
|
ledis> BOPT XOR res3 a b
|
||||||
|
(integer) 3
|
||||||
|
ledis> BCOUNT res3
|
||||||
|
(integer) 3
|
||||||
|
```
|
||||||
|
|
||||||
|
### BCOUNT key [start end]
|
||||||
|
|
||||||
|
Count the number of set bits in a bitmap.
|
||||||
|
|
||||||
|
**Return value**
|
||||||
|
|
||||||
|
int64 : The number of bits set to 1.
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```
|
||||||
|
ledis> BMSETBIT flag 0 1 5 1 6 1
|
||||||
|
(integer) 3
|
||||||
|
ledis> BGET flag
|
||||||
|
a
|
||||||
|
ledis> BCOUNT flag
|
||||||
|
(integer) 3
|
||||||
|
ledis> BCOUNT flag 0 0s
|
||||||
|
(integer) 1
|
||||||
|
ledis> BCOUNT flag 0 4
|
||||||
|
(integer) 1
|
||||||
|
ledis> BCOUNT flag 0 5
|
||||||
|
(integer) 2
|
||||||
|
ledis> BCOUNT flag 5 6
|
||||||
|
(integer) 2
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### BEXPIRE key seconds
|
||||||
|
|
||||||
|
(refer to [EXPIRE](#expire-key-seconds) api for other types)
|
||||||
|
|
||||||
|
|
||||||
|
### BEXPIREAT key timestamp
|
||||||
|
|
||||||
|
(refer to [EXPIREAT](#expireat-key-timestamp) api for other types)
|
||||||
|
|
||||||
|
|
||||||
|
### BTTL key
|
||||||
|
|
||||||
|
(refer to [TTL](#ttl-key) api for other types)
|
||||||
|
|
||||||
|
|
||||||
|
### BPERSIST key
|
||||||
|
|
||||||
|
(refer to [PERSIST](#persist-key) api for other types)
|
||||||
|
|
||||||
|
|
||||||
## Replication
|
## Replication
|
||||||
|
|
||||||
### SLAVEOF host port
|
### SLAVEOF host port
|
||||||
|
|
106
ledis/t_bit.go
106
ledis/t_bit.go
|
@ -371,6 +371,65 @@ func (db *DB) bExpireAt(key []byte, when int64) (int64, error) {
|
||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) bCountByte(val byte, soff uint32, eoff uint32) int32 {
|
||||||
|
if soff > eoff {
|
||||||
|
soff, eoff = eoff, soff
|
||||||
|
}
|
||||||
|
|
||||||
|
mask := uint8(0)
|
||||||
|
if soff > 0 {
|
||||||
|
mask |= fillBits[soff-1]
|
||||||
|
}
|
||||||
|
if eoff < 7 {
|
||||||
|
mask |= (fillBits[7] ^ fillBits[eoff])
|
||||||
|
}
|
||||||
|
mask = fillBits[7] ^ mask
|
||||||
|
|
||||||
|
return bitsInByte[val&mask]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) bCountSeg(key []byte, seq uint32, soff uint32, eoff uint32) (cnt int32, err error) {
|
||||||
|
if soff >= segBitSize || soff < 0 ||
|
||||||
|
eoff >= segBitSize || eoff < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var segment []byte
|
||||||
|
if _, segment, err = db.bGetSegment(key, seq); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if segment == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if soff > eoff {
|
||||||
|
soff, eoff = eoff, soff
|
||||||
|
}
|
||||||
|
|
||||||
|
headIdx := int(soff >> 3)
|
||||||
|
endIdx := int(eoff >> 3)
|
||||||
|
sByteOff := soff - ((soff >> 3) << 3)
|
||||||
|
eByteOff := eoff - ((eoff >> 3) << 3)
|
||||||
|
|
||||||
|
if headIdx == endIdx {
|
||||||
|
cnt = db.bCountByte(segment[headIdx], sByteOff, eByteOff)
|
||||||
|
} else {
|
||||||
|
cnt = db.bCountByte(segment[headIdx], sByteOff, 7) +
|
||||||
|
db.bCountByte(segment[endIdx], 0, eByteOff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sum up following bytes
|
||||||
|
for idx, end := headIdx+1, endIdx-1; idx <= end; idx += 1 {
|
||||||
|
cnt += bitsInByte[segment[idx]]
|
||||||
|
if idx == end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) BGet(key []byte) (data []byte, err error) {
|
func (db *DB) BGet(key []byte) (data []byte, err error) {
|
||||||
if err = checkKeySize(key); err != nil {
|
if err = checkKeySize(key); err != nil {
|
||||||
return
|
return
|
||||||
|
@ -561,25 +620,53 @@ func (db *DB) BGetBit(key []byte, offset int32) (uint8, error) {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func (db *DB) BCount(key []byte, start int32, end int32) (cnt int32, err error) {
|
func (db *DB) BCount(key []byte, start int32, end int32) (cnt int32, err error) {
|
||||||
var sseq uint32
|
var sseq, soff uint32
|
||||||
if sseq, _, err = db.bParseOffset(key, start); err != nil {
|
if sseq, soff, err = db.bParseOffset(key, start); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var eseq uint32
|
var eseq, eoff uint32
|
||||||
if eseq, _, err = db.bParseOffset(key, end); err != nil {
|
if eseq, eoff, err = db.bParseOffset(key, end); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sseq > eseq || (sseq == eseq && soff > eoff) {
|
||||||
|
sseq, eseq = eseq, sseq
|
||||||
|
soff, eoff = eoff, soff
|
||||||
|
}
|
||||||
|
|
||||||
|
var segCnt int32
|
||||||
|
if eseq == sseq {
|
||||||
|
if segCnt, err = db.bCountSeg(key, sseq, soff, eoff); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt = segCnt
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if segCnt, err = db.bCountSeg(key, sseq, soff, segBitSize-1); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
cnt += segCnt
|
||||||
|
}
|
||||||
|
|
||||||
|
if segCnt, err = db.bCountSeg(key, eseq, 0, eoff); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
cnt += segCnt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// middle segs
|
||||||
var segment []byte
|
var segment []byte
|
||||||
skey := db.bEncodeBinKey(key, sseq)
|
skey := db.bEncodeBinKey(key, sseq)
|
||||||
ekey := db.bEncodeBinKey(key, eseq)
|
ekey := db.bEncodeBinKey(key, eseq)
|
||||||
|
|
||||||
it := db.db.RangeIterator(skey, ekey, leveldb.RangeClose)
|
it := db.db.RangeIterator(skey, ekey, leveldb.RangeOpen)
|
||||||
for ; it.Valid(); it.Next() {
|
for ; it.Valid(); it.Next() {
|
||||||
segment = it.Value()
|
segment = it.Value()
|
||||||
for _, bit := range segment {
|
for _, bt := range segment {
|
||||||
cnt += bitsInByte[bit]
|
cnt += bitsInByte[bt]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it.Close()
|
it.Close()
|
||||||
|
@ -604,7 +691,7 @@ func (db *DB) BTail(key []byte) (int32, error) {
|
||||||
|
|
||||||
func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32, err error) {
|
func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32, err error) {
|
||||||
// blen -
|
// blen -
|
||||||
// the size of the string stored in the destination key,
|
// the total bit size of data stored in destination key,
|
||||||
// that is equal to the size of the longest input string.
|
// that is equal to the size of the longest input string.
|
||||||
|
|
||||||
var exeOp func([]byte, []byte, *[]byte)
|
var exeOp func([]byte, []byte, *[]byte)
|
||||||
|
@ -759,7 +846,8 @@ func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32
|
||||||
|
|
||||||
err = t.Commit()
|
err = t.Commit()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
blen = int32(maxDstSeq<<segBitWidth | maxDstOff)
|
// blen = int32(db.bCapByteSize(maxDstOff, maxDstOff))
|
||||||
|
blen = int32(maxDstSeq<<segBitWidth | maxDstOff + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -36,6 +36,7 @@ func newBytes(bitLen int32) []byte {
|
||||||
func TestBinary(t *testing.T) {
|
func TestBinary(t *testing.T) {
|
||||||
testSimple(t)
|
testSimple(t)
|
||||||
testSimpleII(t)
|
testSimpleII(t)
|
||||||
|
testCount(t)
|
||||||
testOpAndOr(t)
|
testOpAndOr(t)
|
||||||
testOpXor(t)
|
testOpXor(t)
|
||||||
testOpNot(t)
|
testOpNot(t)
|
||||||
|
@ -130,6 +131,102 @@ func testSimpleII(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCount(t *testing.T) {
|
||||||
|
db := getTestDB()
|
||||||
|
db.FlushAll()
|
||||||
|
|
||||||
|
key := []byte("test_bin_count")
|
||||||
|
|
||||||
|
if ori, _ := db.BSetBit(key, 0, 1); ori != 0 {
|
||||||
|
t.Error(ori)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ori, _ := db.BSetBit(key, 10, 1); ori != 0 {
|
||||||
|
t.Error(ori)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ori, _ := db.BSetBit(key, 262140, 1); ori != 0 {
|
||||||
|
t.Error(ori)
|
||||||
|
}
|
||||||
|
|
||||||
|
// count
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, -1); sum != 3 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, 9); sum != 1 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, 10); sum != 2 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, 11); sum != 2 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, 262139); sum != 2 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, 262140); sum != 3 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, 262141); sum != 3 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 10, 262140); sum != 2 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 11, 262140); sum != 1 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 11, 262139); sum != 0 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
key = []byte("test_bin_count_2")
|
||||||
|
|
||||||
|
db.BSetBit(key, 1, 1)
|
||||||
|
db.BSetBit(key, 2, 1)
|
||||||
|
db.BSetBit(key, 4, 1)
|
||||||
|
db.BSetBit(key, 6, 1)
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, -1); sum != 4 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 1, 1); sum != 1 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, 7); sum != 4 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ori, _ := db.BSetBit(key, 8, 1); ori != 0 {
|
||||||
|
t.Error(ori)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ori, _ := db.BSetBit(key, 11, 1); ori != 0 {
|
||||||
|
t.Error(ori)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, -1); sum != 6 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum, _ := db.BCount(key, 0, 16); sum != 6 {
|
||||||
|
t.Error(sum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testOpAndOr(t *testing.T) {
|
func testOpAndOr(t *testing.T) {
|
||||||
db := getTestDB()
|
db := getTestDB()
|
||||||
db.FlushAll()
|
db.FlushAll()
|
||||||
|
@ -256,6 +353,30 @@ func testOpAndOr(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testOpAnd(t *testing.T) {
|
||||||
|
db := getTestDB()
|
||||||
|
db.FlushAll()
|
||||||
|
|
||||||
|
dstKey := []byte("test_bin_or")
|
||||||
|
|
||||||
|
k0 := []byte("op_or_0")
|
||||||
|
k1 := []byte("op_or_01")
|
||||||
|
srcKeys := [][]byte{k0, k1}
|
||||||
|
|
||||||
|
db.BSetBit(k0, 0, 1)
|
||||||
|
db.BSetBit(k0, 2, 1)
|
||||||
|
|
||||||
|
db.BSetBit(k1, 1, 1)
|
||||||
|
|
||||||
|
if blen, _ := db.BOperation(OPand, dstKey, srcKeys...); blen != 3 {
|
||||||
|
t.Fatal(blen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt, _ := db.BCount(dstKey, 0, -1); cnt != 1 {
|
||||||
|
t.Fatal(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testOpXor(t *testing.T) {
|
func testOpXor(t *testing.T) {
|
||||||
db := getTestDB()
|
db := getTestDB()
|
||||||
db.FlushAll()
|
db.FlushAll()
|
||||||
|
@ -330,6 +451,22 @@ func testOpNot(t *testing.T) {
|
||||||
if cmpBytes(data, stdData) {
|
if cmpBytes(data, stdData) {
|
||||||
t.Fatal(false)
|
t.Fatal(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
k1 := []byte("op_not_2")
|
||||||
|
srcKeys = [][]byte{k1}
|
||||||
|
|
||||||
|
db.BSetBit(k1, 0, 1)
|
||||||
|
db.BSetBit(k1, 2, 1)
|
||||||
|
db.BSetBit(k1, 4, 1)
|
||||||
|
db.BSetBit(k1, 6, 1)
|
||||||
|
|
||||||
|
if blen, _ := db.BOperation(OPnot, dstKey, srcKeys...); blen != 7 {
|
||||||
|
t.Fatal(blen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt, _ := db.BCount(dstKey, 0, -1); cnt != 3 {
|
||||||
|
t.Fatal(cnt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMSetBit(t *testing.T) {
|
func testMSetBit(t *testing.T) {
|
||||||
|
|
|
@ -79,7 +79,7 @@ func StrInt8(v []byte, err error) (int8, error) {
|
||||||
} else if v == nil {
|
} else if v == nil {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
} else {
|
} else {
|
||||||
res, err := strconv.ParseInt(String(v), 10, 32)
|
res, err := strconv.ParseInt(String(v), 10, 8)
|
||||||
return int8(res), err
|
return int8(res), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBin(t *testing.T) {
|
func TestBit(t *testing.T) {
|
||||||
testBinGetSet(t)
|
testBitGetSet(t)
|
||||||
testBinMset(t)
|
testBitMset(t)
|
||||||
testBinCount(t)
|
testBitCount(t)
|
||||||
testBinOpt(t)
|
testBitOpt(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBinGetSet(t *testing.T) {
|
func testBitGetSet(t *testing.T) {
|
||||||
c := getTestConn()
|
c := getTestConn()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ func testBinGetSet(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBinMset(t *testing.T) {
|
func testBitMset(t *testing.T) {
|
||||||
c := getTestConn()
|
c := getTestConn()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ func testBinMset(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBinCount(t *testing.T) {
|
func testBitCount(t *testing.T) {
|
||||||
c := getTestConn()
|
c := getTestConn()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ func testBinCount(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBinOpt(t *testing.T) {
|
func testBitOpt(t *testing.T) {
|
||||||
c := getTestConn()
|
c := getTestConn()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ func testBinOpt(t *testing.T) {
|
||||||
if blen, err := ledis.Int(
|
if blen, err := ledis.Int(
|
||||||
c.Do("bopt", "and", dstk, k0, k1)); err != nil {
|
c.Do("bopt", "and", dstk, k0, k1)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if blen != 100 {
|
} else if blen != 101 {
|
||||||
t.Fatal(blen)
|
t.Fatal(blen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ func testBinOpt(t *testing.T) {
|
||||||
if blen, err := ledis.Int(
|
if blen, err := ledis.Int(
|
||||||
c.Do("bopt", "or", dstk, k0, k1)); err != nil {
|
c.Do("bopt", "or", dstk, k0, k1)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if blen != 100 {
|
} else if blen != 101 {
|
||||||
t.Fatal(blen)
|
t.Fatal(blen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ func testBinOpt(t *testing.T) {
|
||||||
if blen, err := ledis.Int(
|
if blen, err := ledis.Int(
|
||||||
c.Do("bopt", "xor", dstk, k0, k1)); err != nil {
|
c.Do("bopt", "xor", dstk, k0, k1)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if blen != 100 {
|
} else if blen != 101 {
|
||||||
t.Fatal(blen)
|
t.Fatal(blen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue