├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd └── server │ └── main.go ├── config.toml ├── config ├── config.go └── config_test.go ├── docs └── tidis-arch.png ├── go.mod ├── runtest ├── server ├── app.go ├── client.go ├── command.go ├── command_hash.go ├── command_list.go ├── command_server.go ├── command_set.go ├── command_string.go └── command_zset.go ├── store ├── db.go ├── store.go └── tikv │ ├── iterator.go │ ├── iterator_test.go │ ├── tikv.go │ └── tikv_test.go ├── terror └── terror.go ├── tests ├── rediswrap.py ├── test_hash.py ├── test_helper.py ├── test_list.py ├── test_set.py ├── test_string.py ├── test_txn.py └── test_zset.py ├── tidis ├── async.go ├── codec.go ├── gc.go ├── leader.go ├── t_hash.go ├── t_list.go ├── t_object.go ├── t_set.go ├── t_string.go ├── t_zset.go ├── tidis.go ├── ttl.go └── type.go └── utils └── time.go /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/server/server 2 | *.pyc 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: go 4 | 5 | go_import_path: github.com/yongman/tidis 6 | 7 | go: 8 | - "1.13" 9 | 10 | git: 11 | depth: 1 12 | 13 | notifications: 14 | email: false 15 | 16 | script: 17 | - env GO111MODULE=on make 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.15-alpine3.12 as builder 2 | 3 | RUN apk add --no-cache make git 4 | 5 | COPY . /go/src/github.com/yongman/tidis 6 | 7 | WORKDIR /go/src/github.com/yongman/tidis/ 8 | 9 | RUN make 10 | 11 | FROM scratch 12 | COPY --from=builder /go/src/github.com/yongman/tidis/bin/tidis-server /tidis-server 13 | 14 | WORKDIR / 15 | 16 | EXPOSE 5379 17 | 18 | ENTRYPOINT ["/tidis-server"] 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 yongman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # yongman, 2018-04-17 15:05 4 | # 5 | 6 | all: build 7 | 8 | build: 9 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -gcflags "all=-N -l" -o bin/tidis-server cmd/server/* 10 | 11 | # vim:ft=make 12 | # 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 💔💔💔 This repo is archived and no longer maintained. 2 | # ❤️❤️❤️ Please refer to the totally new version with more features maintained by PingCAP officially. 3 | # [https://github.com/tidb-incubator/tidis](https://github.com/tidb-incubator/tidis) 4 | 5 | [![Build Status](https://travis-ci.org/yongman/tidis.svg?branch=master)](https://travis-ci.org/yongman/tidis) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/yongman/tidis)](https://goreportcard.com/report/github.com/yongman/tidis) 7 | ![Project Status](https://img.shields.io/badge/status-alpha-green.svg) 8 | 9 | # What is Tidis? 10 | 11 | Tidis is a Distributed NoSQL database, providing a Redis protocol API (string, list, hash, set, sorted set), written in Go. 12 | 13 | Tidis is like [TiDB](https://github.com/pingcap/tidb) layer, providing protocol transform and data structure compute, powered by [TiKV](https://github.com/pingcap/tikv) backend distributed storage which use Raft for data replication and 2PC for distributed transaction. 14 | 15 | ## Features 16 | 17 | * Redis protocol compatible 18 | * Linear scale-out ability 19 | * Storage and computation separation 20 | * Data safety, no data loss, Raft replication 21 | * Transaction support 22 | 23 | Any pull requests are welcomed. 24 | 25 | ## Architecture 26 | 27 | *Architechture of tidis* 28 | 29 | ![architecture](docs/tidis-arch.png) 30 | 31 | *Architechture of tikv* 32 | 33 | ![](https://pingcap.com/images/blog/TiKV_%20Architecture.png) 34 | - Placement Driver (PD): PD is the brain of the TiKV system which manages the metadata about Nodes, Stores, Regions mapping, and makes decisions for data placement and load balancing. PD periodically checks replication constraints to balance load and data automatically. 35 | - Node: A physical node in the cluster. Within each node, there are one or more Stores. Within each Store, there are many Regions. 36 | - Store: There is a RocksDB within each Store and it stores data in local disks. 37 | - Region: Region is the basic unit of Key-Value data movement and corresponds to a data range in a Store. Each Region is replicated to multiple Nodes. These multiple replicas form a Raft group. A replica of a Region is called a Peer. 38 | 39 | 40 | ## 1. Run tidis with docker-compose in one command 41 | 42 | ``` 43 | git clone https://github.com/yongman/tidis-docker-compose.git 44 | cd tidis-docker-compose/ 45 | docker-compose up -d 46 | ``` 47 | 48 | Or follow [tidis-docker-compose](https://github.com/yongman/tidis-docker-compose) guide to run with `docker-compose` 49 | 50 | ## 2. Build or Docker mannually 51 | 52 | ### Build from source 53 | 54 | ``` 55 | git clone https://github.com/yongman/tidis.git 56 | cd tidis && make 57 | ``` 58 | 59 | ### Pull from docker 60 | 61 | ``` 62 | docker pull yongman/tidis 63 | ``` 64 | 65 | ### Run TiKV cluster for test 66 | 67 | Use `docker run tikv` for test, just follow [PingCAP official guide](https://pingcap.com/docs/stable/how-to/deploy/orchestrated/docker/), you just need to deploy PD and TiKV servers, Tidis will take the role of TiDB. 68 | 69 | ### Run Tidis or docker 70 | 71 | #### Run tidis from executable file 72 | 73 | ``` 74 | bin/tidis-server -conf config.toml 75 | ``` 76 | 77 | #### Run tidis from docker 78 | 79 | ``` 80 | docker run -d --name tidis -p 5379:5379 -v {your_config_dir}:/data yongman/tidis -conf="/data/config.toml" 81 | ``` 82 | 83 | ## 3. Client request 84 | 85 | ``` 86 | redis-cli -p 5379 87 | 127.0.0.1:5379> get a 88 | "1" 89 | 127.0.0.1:5379> lrange l 0 -1 90 | 1) "6" 91 | 2) "5" 92 | 3) "4" 93 | 127.0.0.1:5379> zadd zzz 1 1 2 2 3 3 4 4 94 | (integer) 4 95 | 127.0.0.1:5379> zcard zzz 96 | (integer) 4 97 | 127.0.0.1:5379> zincrby zzz 10 1 98 | (integer) 11 99 | 127.0.0.1:5379> zrange zzz 0 -1 withscores 100 | 1) "2" 101 | 2) "2" 102 | 3) "3" 103 | 4) "3" 104 | 5) "4" 105 | 6) "4" 106 | 7) "1" 107 | 8) "11" 108 | ``` 109 | 110 | 111 | ## Already supported commands 112 | 113 | ### Keys 114 | 115 | +-----------+-------------------------------------+ 116 | | pexpire | pexpire key int | 117 | +-----------+-------------------------------------+ 118 | | pexpireat | pexpireat key timestamp(ms) | 119 | +-----------+-------------------------------------+ 120 | | expire | expire key int | 121 | +-----------+-------------------------------------+ 122 | | expireat | expireat key timestamp(s) | 123 | +-----------+-------------------------------------+ 124 | | pttl | pttl key | 125 | +-----------+-------------------------------------+ 126 | | ttl | ttl key | 127 | +-----------+-------------------------------------+ 128 | | type | type key | 129 | +-----------+-------------------------------------+ 130 | 131 | ### String 132 | 133 | +-----------+-------------------------------------+ 134 | | command | format | 135 | +-----------+-------------------------------------+ 136 | | get | get key | 137 | +-----------+-------------------------------------+ 138 | | set | set key value [EX sec|PX ms][NX|XX] | 139 | +-----------+-------------------------------------+ 140 | | getbit | getbit key offset | 141 | +-----------+-------------------------------------+ 142 | | setbit | setbit key offset value | 143 | +-----------+-------------------------------------+ 144 | | del | del key1 key2 ... | 145 | +-----------+-------------------------------------+ 146 | | mget | mget key1 key2 ... | 147 | +-----------+-------------------------------------+ 148 | | mset | mset key1 value1 key2 value2 ... | 149 | +-----------+-------------------------------------+ 150 | | incr | incr key | 151 | +-----------+-------------------------------------+ 152 | | incrby | incr key step | 153 | +-----------+-------------------------------------+ 154 | | decr | decr key | 155 | +-----------+-------------------------------------+ 156 | | decrby | decrby key step | 157 | +-----------+-------------------------------------+ 158 | | strlen | strlen key | 159 | +-----------+-------------------------------------+ 160 | 161 | 162 | ### Hash 163 | 164 | +------------+------------------------------------------+ 165 | | Commands | Format | 166 | +------------+------------------------------------------+ 167 | | hget | hget key field | 168 | +------------+------------------------------------------+ 169 | | hstrlen | hstrlen key | 170 | +------------+------------------------------------------+ 171 | | hexists | hexists key | 172 | +------------+------------------------------------------+ 173 | | hlen | hlen key | 174 | +------------+------------------------------------------+ 175 | | hmget | hmget key field1 field2 field3... | 176 | +------------+------------------------------------------+ 177 | | hdel | hdel key field1 field2 field3... | 178 | +------------+------------------------------------------+ 179 | | hset | hset key field value | 180 | +------------+------------------------------------------+ 181 | | hsetnx | hsetnx key field value | 182 | +------------+------------------------------------------+ 183 | | hmset | hmset key field1 value1 field2 value2... | 184 | +------------+------------------------------------------+ 185 | | hkeys | hkeys key | 186 | +------------+------------------------------------------+ 187 | | hvals | hvals key | 188 | +------------+------------------------------------------+ 189 | | hgetall | hgetall key | 190 | +------------+------------------------------------------+ 191 | 192 | ### List 193 | 194 | +------------+-----------------------+ 195 | | commands | format | 196 | +------------+-----------------------+ 197 | | lpop | lpop key | 198 | +------------+-----------------------+ 199 | | rpush | rpush key | 200 | +------------+-----------------------+ 201 | | rpop | rpop key | 202 | +------------+-----------------------+ 203 | | llen | llen key | 204 | +------------+-----------------------+ 205 | | lindex | lindex key index | 206 | +------------+-----------------------+ 207 | | lrange | lrange key start stop | 208 | +------------+-----------------------+ 209 | | lset | lset key index value | 210 | +------------+-----------------------+ 211 | | ltrim | ltrim key start stop | 212 | +------------+-----------------------+ 213 | 214 | ### Set 215 | 216 | +-------------+--------------------------------+ 217 | | commands | format | 218 | +-------------+--------------------------------+ 219 | | sadd | sadd key member1 [member2 ...] | 220 | +-------------+--------------------------------+ 221 | | scard | scard key | 222 | +-------------+--------------------------------+ 223 | | sismember | sismember key member | 224 | +-------------+--------------------------------+ 225 | | smembers | smembers key | 226 | +-------------+--------------------------------+ 227 | | srem | srem key member | 228 | +-------------+--------------------------------+ 229 | | sdiff | sdiff key1 key2 | 230 | +-------------+--------------------------------+ 231 | | sunion | sunion key1 key2 | 232 | +-------------+--------------------------------+ 233 | | sinter | sinter key1 key2 | 234 | +-------------+--------------------------------+ 235 | | sdiffstore | sdiffstore key1 key2 key3 | 236 | +-------------+--------------------------------+ 237 | | sunionstore | sunionstore key1 key2 key3 | 238 | +-------------+--------------------------------+ 239 | | sinterstore | sinterstore key1 key2 key3 | 240 | +-------------+--------------------------------+ 241 | 242 | ### Sorted set 243 | 244 | +------------------+---------------------------------------------------------------+ 245 | | commands | format | 246 | +------------------+---------------------------------------------------------------+ 247 | | zadd | zadd key member1 score1 [member2 score2 ...] | 248 | +------------------+---------------------------------------------------------------+ 249 | | zcard | zcard key | 250 | +------------------+---------------------------------------------------------------+ 251 | | zrange | zrange key start stop [WITHSCORES] | 252 | +------------------+---------------------------------------------------------------+ 253 | | zrevrange | zrevrange key start stop [WITHSCORES] | 254 | +------------------+---------------------------------------------------------------+ 255 | | zrangebyscore | zrangebyscore key min max [WITHSCORES][LIMIT offset count] | 256 | +------------------+---------------------------------------------------------------+ 257 | | zrevrangebyscore | zrevrangebyscore key max min [WITHSCORES][LIMIT offset count] | 258 | +------------------+---------------------------------------------------------------+ 259 | | zremrangebyscore | zremrangebyscore key min max | 260 | +------------------+---------------------------------------------------------------+ 261 | | zrangebylex | zrangebylex key min max [LIMIT offset count] | 262 | +------------------+---------------------------------------------------------------+ 263 | | zrevrangebylex | zrevrangebylex key max min [LIMIT offset count] | 264 | +------------------+---------------------------------------------------------------+ 265 | | zremrangebylex | zremrangebylex key min max | 266 | +------------------+---------------------------------------------------------------+ 267 | | zcount | zcount key | 268 | +------------------+---------------------------------------------------------------+ 269 | | zlexcount | zlexcount key | 270 | +------------------+---------------------------------------------------------------+ 271 | | zscore | zscore key member | 272 | +------------------+---------------------------------------------------------------+ 273 | | zrem | zrem key member1 [member2 ...] | 274 | +------------------+---------------------------------------------------------------+ 275 | | zincrby | zincrby key increment member | 276 | +------------------+---------------------------------------------------------------+ 277 | 278 | ### Transaction 279 | 280 | +---------+---------+ 281 | | command | support | 282 | +---------+---------+ 283 | | multi | Yes | 284 | +---------+---------+ 285 | | exec | Yes | 286 | +---------+---------+ 287 | 288 | ### Server & Connections 289 | 290 | +-----------+---------------+ 291 | | command | format | 292 | +-----------+---------------+ 293 | | flushdb | flushdb id | 294 | +-----------+---------------+ 295 | | flushall | flush | 296 | +-----------+---------------+ 297 | | select | select id | 298 | +-----------+---------------+ 299 | 300 | ## Benchmark 301 | 302 | [base benchmark](https://github.com/yongman/tidis/wiki/Tidis-base-benchmark) 303 | 304 | ## License 305 | 306 | Tidis is under the MIT license. See the [LICENSE](./LICENSE) file for details. 307 | 308 | ## Acknowledgment 309 | 310 | * Thanks [PingCAP](https://github.com/pingcap) for providing [tikv](https://github.com/pingcap/tikv) and [pd](https://github.com/pingcap/pd) powerful components. 311 | * Thanks [RocksDB](https://github.com/facebook/rocksdb) for their powerful storage engines. 312 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // main.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package main 9 | 10 | import ( 11 | "flag" 12 | "os" 13 | "os/signal" 14 | "syscall" 15 | 16 | "net/http" 17 | 18 | "github.com/yongman/go/log" 19 | "github.com/yongman/tidis/config" 20 | "github.com/yongman/tidis/server" 21 | 22 | _ "net/http/pprof" 23 | ) 24 | 25 | var ( 26 | listen string 27 | backend string 28 | txnRetry int 29 | conf string 30 | loglevel string 31 | auth string 32 | debug bool 33 | ) 34 | 35 | func init() { 36 | flag.StringVar(&listen, "listen", "", "server listen address") 37 | flag.StringVar(&backend, "backend", "", "tikv storage backend address") 38 | flag.IntVar(&txnRetry, "retry", 5, "transaction retry time when commit failed") 39 | flag.StringVar(&conf, "conf", "", "config file") 40 | flag.StringVar(&loglevel, "loglevel", "info", "loglevel output, format:info/debug/warn") 41 | flag.StringVar(&auth, "auth", "", "connection authentication") 42 | flag.BoolVar(&debug, "debug", false, "run tidis server in debug mode") 43 | } 44 | 45 | func setLogLevel(loglevel string) { 46 | switch loglevel { 47 | case "info": 48 | log.SetLevel(log.INFO) 49 | case "debug": 50 | log.SetLevel(log.DEBUG) 51 | case "warn": 52 | log.SetLevel(log.WARN) 53 | default: 54 | log.SetLevel(log.INFO) 55 | } 56 | } 57 | 58 | func main() { 59 | flag.Parse() 60 | 61 | log.Info("server started") 62 | 63 | var ( 64 | c *config.Config 65 | err error 66 | ) 67 | 68 | if conf != "" { 69 | c, err = config.LoadConfig(conf) 70 | if err != nil { 71 | return 72 | } 73 | } else { 74 | if c == nil && backend == "" { 75 | log.Fatal("backend argument must be assign") 76 | } 77 | } 78 | c = config.NewConfig(c, listen, backend, txnRetry, auth) 79 | 80 | config.FillWithDefaultConfig(c) 81 | 82 | setLogLevel(c.Tidis.LogLevel) 83 | 84 | app := server.NewApp(c) 85 | 86 | quitCh := make(chan os.Signal, 1) 87 | signal.Notify(quitCh, os.Kill, os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) 88 | 89 | if debug { 90 | go http.ListenAndServe("0.0.0.0:11111", nil) 91 | } 92 | 93 | go app.Run() 94 | 95 | <-quitCh 96 | } 97 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # sample configuration file for tidis 2 | 3 | desc = "sample configuration for tidis" 4 | 5 | [tidis] 6 | 7 | listen = ":5379" 8 | max_connection = 5000 9 | 10 | loglevel = "info" 11 | 12 | #transaction retry count when commit failed in case of conflict 13 | txn_retry = 2 14 | 15 | auth = "" 16 | 17 | #tenant will be used as the key prefix, in case one tidis used by multiple apps 18 | #TODO 19 | tenantid = "" 20 | 21 | #tikv gc and leader check configure 22 | db_gc_enabled = true 23 | db_gc_interval = 600 24 | db_gc_safepoint_life_time = 600 25 | leader_check_interval = 30 26 | leader_lease_duration = 60 27 | 28 | [backend] 29 | #tikv placement driver addresses 30 | pds = "127.0.0.1:2379" 31 | 32 | 33 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | // 2 | // config.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package config 9 | 10 | import ( 11 | "github.com/BurntSushi/toml" 12 | "github.com/yongman/go/log" 13 | ) 14 | 15 | type Config struct { 16 | Desc string 17 | Tidis tidisConfig `toml:"tidis"` 18 | Backend backendConfig `toml:"backend"` 19 | } 20 | 21 | type tidisConfig struct { 22 | Listen string 23 | MaxConn int32 `toml:"max_connection"` 24 | Auth string `toml:"auth"` 25 | LogLevel string `toml:"loglevel"` 26 | TxnRetry int `toml:"txn_retry"` 27 | TenantId string `toml:"tenantid"` 28 | LeaderCheckInterval int `toml:"leader_check_interval"` 29 | LeaderLeaseDuration int `toml:"leader_lease_duration"` 30 | DBGCEnabled bool `toml:"db_gc_enabled"` 31 | DBGcInterval int `toml:"db_gc_interval"` 32 | DBGcConcurrency int `toml:"db_gc_concurrency"` 33 | DBSafePointLifeTime int `toml:"db_gc_safepoint_life_time"` 34 | } 35 | 36 | type backendConfig struct { 37 | Pds string 38 | } 39 | 40 | func LoadConfig(path string) (*Config, error) { 41 | var c Config 42 | if _, err := toml.DecodeFile(path, &c); err != nil { 43 | log.Errorf("config file parse failed, %v", err) 44 | return nil, err 45 | } 46 | return &c, nil 47 | } 48 | 49 | func NewConfig(c *Config, listen, addr string, retry int, auth string) *Config { 50 | if c == nil { 51 | backend := backendConfig{ 52 | Pds: addr, 53 | } 54 | tidis := tidisConfig{ 55 | Listen: listen, 56 | MaxConn: 0, 57 | Auth: auth, 58 | LeaderCheckInterval: 30, 59 | LeaderLeaseDuration: 60, 60 | DBGCEnabled: true, 61 | DBGcInterval: 10*60, 62 | DBGcConcurrency: 3, 63 | DBSafePointLifeTime: 10*60, 64 | } 65 | c = &Config{ 66 | Desc: "new config", 67 | Tidis: tidis, 68 | Backend: backend, 69 | } 70 | } else { 71 | // update config load previous 72 | if listen != "" { 73 | c.Tidis.Listen = listen 74 | } 75 | if addr != "" { 76 | c.Backend.Pds = addr 77 | } 78 | if addr != "" { 79 | c.Tidis.Auth = auth 80 | } 81 | if retry != 0 { 82 | c.Tidis.TxnRetry = retry 83 | } 84 | 85 | // set gc default configure 86 | if c.Tidis.LeaderLeaseDuration == 0 { 87 | c.Tidis.LeaderLeaseDuration = 30 88 | } 89 | if c.Tidis.LeaderCheckInterval == 0 { 90 | c.Tidis.LeaderCheckInterval = 60 91 | } 92 | if c.Tidis.DBGcConcurrency == 0 { 93 | c.Tidis.DBGcConcurrency = 3 94 | } 95 | if c.Tidis.DBGcInterval == 0 { 96 | c.Tidis.DBGcInterval = 10*60 97 | } 98 | if c.Tidis.DBSafePointLifeTime == 0 { 99 | c.Tidis.DBSafePointLifeTime = 10*60 100 | } 101 | } 102 | return c 103 | } 104 | 105 | // update config fields with default value if not filled 106 | func FillWithDefaultConfig(c *Config) { 107 | if c.Tidis.Listen == "" { 108 | c.Tidis.Listen = ":5379" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // config_test.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package config 9 | 10 | import ( 11 | "fmt" 12 | "testing" 13 | ) 14 | 15 | func TestLoadConfig(t *testing.T) { 16 | conf, err := LoadConfig("../config.toml") 17 | fmt.Println(*conf, err) 18 | if err == nil { 19 | FillWithDefaultConfig(conf) 20 | fmt.Println(*conf) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/tidis-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongman/tidis/405e53a63aa637b055d0e4c35333832d853fa6c6/docs/tidis-arch.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yongman/tidis 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/BurntSushi/toml v0.3.1 7 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 8 | github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 // indirect 9 | github.com/alicebob/miniredis v2.5.0+incompatible // indirect 10 | github.com/coreos/go-semver v0.3.0 // indirect 11 | github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8 // indirect 12 | github.com/deckarep/golang-set v1.7.1 13 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect 14 | github.com/go-ole/go-ole v1.2.4 // indirect 15 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 // indirect 16 | github.com/gomodule/redigo v2.0.0+incompatible // indirect 17 | github.com/google/uuid v1.1.1 18 | github.com/gorilla/websocket v1.4.1 // indirect 19 | github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 // indirect 20 | github.com/grpc-ecosystem/grpc-gateway v1.12.1 // indirect 21 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect 22 | github.com/onsi/ginkgo v1.11.0 // indirect 23 | github.com/onsi/gomega v1.8.1 // indirect 24 | github.com/pingcap/errors v0.11.5-0.20190809092503-95897b64e011 // indirect 25 | github.com/pingcap/failpoint v0.0.0-20191029060244-12f4ac2fd11d // indirect 26 | github.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989 // indirect 27 | github.com/pingcap/kvproto v0.0.0-20200116032135-1082c388cb01 28 | github.com/pingcap/tidb v1.1.0-beta.0.20200109142221-bf155a7773d8 29 | github.com/prometheus/client_golang v1.3.0 // indirect 30 | github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect 31 | github.com/shirou/gopsutil v2.19.10+incompatible // indirect 32 | github.com/spaolacci/murmur3 v1.1.0 // indirect 33 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect 34 | github.com/uber/jaeger-client-go v2.22.1+incompatible // indirect 35 | github.com/uber/jaeger-lib v2.2.0+incompatible // indirect 36 | github.com/yongman/go v0.0.0-20201103083454-5d5d8ef62542 37 | github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect 38 | go.etcd.io/etcd v0.5.0-alpha.5.0.20191023171146-3cf2f69b5738 // indirect 39 | go.uber.org/atomic v1.5.1 // indirect 40 | go.uber.org/multierr v1.4.0 // indirect 41 | go.uber.org/zap v1.13.0 // indirect 42 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect 43 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect 44 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect 45 | golang.org/x/text v0.3.2 // indirect 46 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect 47 | golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9 // indirect 48 | google.golang.org/grpc v1.26.0 // indirect 49 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /runtest: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | python tests/test_helper.py 4 | -------------------------------------------------------------------------------- /server/app.go: -------------------------------------------------------------------------------- 1 | // 2 | // app.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package server 9 | 10 | import ( 11 | "context" 12 | "net" 13 | "sync" 14 | "sync/atomic" 15 | 16 | "github.com/yongman/go/log" 17 | "github.com/yongman/tidis/config" 18 | "github.com/yongman/tidis/tidis" 19 | ) 20 | 21 | type App struct { 22 | conf *config.Config 23 | 24 | listener net.Listener 25 | 26 | // wrapper and manager for db instance 27 | tdb *tidis.Tidis 28 | 29 | // connection authentication 30 | auth string 31 | 32 | tenanId string 33 | 34 | quitCh chan bool 35 | 36 | clientWG sync.WaitGroup 37 | 38 | clientCount int32 39 | 40 | //client map? 41 | } 42 | 43 | // initialize an app 44 | func NewApp(conf *config.Config) *App { 45 | var err error 46 | app := &App{ 47 | conf: conf, 48 | auth: conf.Tidis.Auth, 49 | } 50 | 51 | app.tdb, err = tidis.NewTidis(conf) 52 | if err != nil { 53 | log.Fatal(err.Error()) 54 | } 55 | 56 | app.listener, err = net.Listen("tcp", conf.Tidis.Listen) 57 | log.Infof("server listen in %s", conf.Tidis.Listen) 58 | if err != nil { 59 | log.Fatal(err.Error()) 60 | } 61 | 62 | return app 63 | } 64 | 65 | func (app *App) GetTidis() *tidis.Tidis { 66 | return app.tdb 67 | } 68 | 69 | func (app *App) Close() error { 70 | return nil 71 | } 72 | 73 | func (app *App) Run() { 74 | ctx, cancel := context.WithCancel(context.Background()) 75 | defer cancel() 76 | 77 | go app.tdb.RunAsync(ctx) 78 | 79 | // run leader checker 80 | leaderChecker := tidis.NewLeaderChecker(app.conf.Tidis.LeaderCheckInterval, 81 | app.conf.Tidis.LeaderLeaseDuration, 82 | app.tdb) 83 | go leaderChecker.Run(ctx) 84 | 85 | // run gc checker 86 | gcChecker := tidis.NewGCChecker(app.conf.Tidis.DBGcInterval, 87 | app.conf.Tidis.DBSafePointLifeTime, 88 | app.conf.Tidis.DBGcConcurrency, 89 | app.tdb) 90 | go gcChecker.Run(ctx) 91 | 92 | 93 | var currentClients int32 94 | 95 | // accept connections 96 | for { 97 | select { 98 | case <-app.quitCh: 99 | return 100 | default: 101 | // accept new client connect and perform 102 | log.Debug("waiting for new connection") 103 | conn, err := app.listener.Accept() 104 | if err != nil { 105 | log.Error(err.Error()) 106 | continue 107 | } 108 | currentClients = atomic.LoadInt32(&app.clientCount) 109 | if app.conf.Tidis.MaxConn > 0 && currentClients > app.conf.Tidis.MaxConn { 110 | log.Warnf("too many client connections, max client connections:%d, now:%d, reject it.", app.conf.Tidis.MaxConn, currentClients) 111 | conn.Close() 112 | continue 113 | } 114 | // handle conn 115 | log.Debug("handle new connection") 116 | ClientHandler(conn, app) 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /server/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // client.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package server 9 | 10 | import ( 11 | "bufio" 12 | "bytes" 13 | "io" 14 | "net" 15 | "strings" 16 | "sync/atomic" 17 | "time" 18 | 19 | "context" 20 | 21 | "github.com/pingcap/tidb/kv" 22 | "github.com/yongman/go/goredis" 23 | "github.com/yongman/go/log" 24 | "github.com/yongman/tidis/terror" 25 | "github.com/yongman/tidis/tidis" 26 | ) 27 | 28 | type Command struct { 29 | cmd string 30 | args [][]byte 31 | } 32 | 33 | type Client struct { 34 | app *App 35 | 36 | tdb *tidis.Tidis 37 | 38 | dbId uint8 39 | 40 | // request is processing 41 | cmd string 42 | args [][]byte 43 | 44 | // transaction block 45 | isTxn bool 46 | cmds []Command 47 | txn kv.Transaction 48 | respTxn []interface{} 49 | 50 | // connection authentation 51 | isAuthed bool 52 | 53 | buf bytes.Buffer 54 | 55 | conn net.Conn 56 | 57 | rReader *goredis.RespReader 58 | rWriter *goredis.RespWriter 59 | } 60 | 61 | func newClient(app *App) *Client { 62 | authed := false 63 | if app.auth == "" { 64 | authed = true 65 | } 66 | 67 | client := &Client{ 68 | app: app, 69 | tdb: app.tdb, 70 | isAuthed: authed, 71 | dbId: 0, 72 | } 73 | return client 74 | } 75 | 76 | func ClientHandler(conn net.Conn, app *App) { 77 | c := newClient(app) 78 | 79 | c.conn = conn 80 | // connection buffer setting 81 | 82 | br := bufio.NewReader(conn) 83 | c.rReader = goredis.NewRespReader(br) 84 | 85 | bw := bufio.NewWriter(conn) 86 | c.rWriter = goredis.NewRespWriter(bw) 87 | 88 | app.clientWG.Add(1) 89 | atomic.AddInt32(&app.clientCount, 1) 90 | 91 | go c.connHandler() 92 | } 93 | 94 | // for multi transaction commands 95 | func (c *Client) NewTxn() error { 96 | var ok bool 97 | txn, err := c.tdb.NewTxn() 98 | c.txn, ok = txn.(kv.Transaction) 99 | if !ok { 100 | return terror.ErrBackendType 101 | } 102 | return err 103 | } 104 | 105 | func (c *Client) GetCurrentTxn() kv.Transaction { 106 | if c.isTxn { 107 | return c.txn 108 | } 109 | return nil 110 | } 111 | 112 | func (c *Client) addResp(resp interface{}) { 113 | c.respTxn = append(c.respTxn, resp) 114 | } 115 | 116 | func (c *Client) CommitTxn() error { 117 | return c.txn.Commit(context.Background()) 118 | } 119 | 120 | func (c *Client) RollbackTxn() error { 121 | return c.txn.Rollback() 122 | } 123 | 124 | func (c *Client) IsTxn() bool { 125 | return c.isTxn 126 | } 127 | 128 | func (c *Client) Resp(resp interface{}) error { 129 | var err error 130 | 131 | if c.isTxn { 132 | c.addResp(resp) 133 | } else { 134 | switch v := resp.(type) { 135 | case []interface{}: 136 | err = c.rWriter.WriteArray(v) 137 | case []byte: 138 | err = c.rWriter.WriteBulk(v) 139 | case nil: 140 | err = c.rWriter.WriteBulk(nil) 141 | case int64: 142 | err = c.rWriter.WriteInteger(v) 143 | case string: 144 | err = c.rWriter.WriteString(v) 145 | case error: 146 | err = c.rWriter.WriteError(v) 147 | default: 148 | err = terror.ErrUnknownType 149 | } 150 | } 151 | 152 | return err 153 | } 154 | 155 | func (c *Client) FlushResp(resp interface{}) error { 156 | err := c.Resp(resp) 157 | if err != nil { 158 | return err 159 | } 160 | return c.rWriter.Flush() 161 | } 162 | 163 | // treat string as bulk array 164 | func (c *Client) Resp1(resp interface{}) error { 165 | var err error 166 | 167 | if c.isTxn { 168 | c.addResp(resp) 169 | } else { 170 | switch v := resp.(type) { 171 | case []interface{}: 172 | err = c.rWriter.WriteArray(v) 173 | case []byte: 174 | err = c.rWriter.WriteBulk(v) 175 | case nil: 176 | err = c.rWriter.WriteBulk(nil) 177 | case int64: 178 | err = c.rWriter.WriteInteger(v) 179 | case string: 180 | err = c.rWriter.WriteBulk([]byte(v)) 181 | case error: 182 | err = c.rWriter.WriteError(v) 183 | default: 184 | err = terror.ErrUnknownType 185 | } 186 | } 187 | 188 | return err 189 | } 190 | func (c *Client) connHandler() { 191 | 192 | defer func(c *Client) { 193 | c.conn.Close() 194 | c.app.clientWG.Done() 195 | atomic.AddInt32(&c.app.clientCount, -1) 196 | }(c) 197 | 198 | select { 199 | case <-c.app.quitCh: 200 | return 201 | default: 202 | break 203 | } 204 | 205 | for { 206 | c.cmd = "" 207 | c.args = nil 208 | 209 | req, err := c.rReader.ParseRequest() 210 | if err != nil && strings.Contains(err.Error(), "short resp line") { 211 | continue 212 | } else if err != nil && err != io.EOF { 213 | log.Error(err.Error()) 214 | return 215 | } else if err != nil { 216 | return 217 | } 218 | err = c.handleRequest(req) 219 | if err != nil && err != io.EOF { 220 | log.Error(err.Error()) 221 | return 222 | } 223 | } 224 | } 225 | 226 | func (c *Client) resetTxnStatus() { 227 | c.isTxn = false 228 | c.cmds = []Command{} 229 | c.respTxn = []interface{}{} 230 | } 231 | 232 | func (c *Client) handleRequest(req [][]byte) error { 233 | if len(req) == 0 { 234 | c.cmd = "" 235 | c.args = nil 236 | } else { 237 | c.cmd = strings.ToLower(string(req[0])) 238 | c.args = req[1:] 239 | } 240 | 241 | // auth check 242 | if c.cmd != "auth" { 243 | if !c.isAuthed { 244 | c.FlushResp(terror.ErrAuthReqired) 245 | return nil 246 | } 247 | } 248 | 249 | var err error 250 | 251 | log.Debugf("command: %s argc:%d", c.cmd, len(c.args)) 252 | switch c.cmd { 253 | case "multi": 254 | // mark connection as transactional 255 | log.Debugf("client in transaction") 256 | c.isTxn = true 257 | c.cmds = []Command{} 258 | c.respTxn = []interface{}{} 259 | 260 | c.rWriter.FlushString("OK") 261 | return nil 262 | case "exec": 263 | if !c.isTxn { 264 | c.FlushResp(terror.ErrExecWithoutMulti) 265 | return nil 266 | } 267 | err = c.NewTxn() 268 | if err != nil { 269 | c.resetTxnStatus() 270 | c.rWriter.FlushBulk(nil) 271 | return nil 272 | } 273 | 274 | // execute transactional commands in txn 275 | // execute commands 276 | log.Debugf("command length:%d txn:%v", len(c.cmds), c.isTxn) 277 | if len(c.cmds) == 0 || !c.isTxn { 278 | c.rWriter.FlushBulk(nil) 279 | c.resetTxnStatus() 280 | return nil 281 | } 282 | 283 | for _, cmd := range c.cmds { 284 | log.Debugf("execute command: %s", cmd.cmd) 285 | // set cmd and args processing 286 | c.cmd = cmd.cmd 287 | c.args = cmd.args 288 | if err = c.execute(); err != nil { 289 | break 290 | } 291 | } 292 | if err != nil { 293 | c.RollbackTxn() 294 | c.rWriter.FlushBulk(nil) 295 | } else { 296 | err = c.CommitTxn() 297 | if err == nil { 298 | c.rWriter.FlushArray(c.respTxn) 299 | } else { 300 | c.rWriter.FlushBulk(nil) 301 | } 302 | } 303 | 304 | c.resetTxnStatus() 305 | return nil 306 | 307 | case "discard": 308 | // discard transactional commands 309 | if c.isTxn { 310 | c.resetTxnStatus() 311 | c.FlushResp("OK") 312 | } else { 313 | c.FlushResp(terror.ErrDiscardWithoutMulti) 314 | } 315 | return nil 316 | 317 | case "auth": 318 | // auth connection 319 | if len(c.args) != 1 { 320 | c.FlushResp(terror.ErrCmdParams) 321 | } 322 | if c.app.auth == "" { 323 | c.FlushResp(terror.ErrAuthNoNeed) 324 | } else if string(c.args[0]) != c.app.auth { 325 | c.isAuthed = false 326 | c.FlushResp(terror.ErrAuthFailed) 327 | } else { 328 | c.isAuthed = true 329 | c.FlushResp("OK") 330 | } 331 | return nil 332 | 333 | case "ping": 334 | if len(c.args) != 0 { 335 | c.FlushResp(terror.ErrCmdParams) 336 | } else { 337 | c.FlushResp("PONG") 338 | } 339 | return nil 340 | case "echo": 341 | if len(c.args) != 1 { 342 | c.FlushResp(terror.ErrCmdParams) 343 | } else { 344 | c.FlushResp(c.args[0]) 345 | } 346 | return nil 347 | case "info": 348 | if len(c.args) == 1 { 349 | c.FlushResp([]byte("# Cluster\r\ncluster_enabled:0\r\n")) 350 | } else { 351 | c.FlushResp([]byte("# Server\r\nredis_mode:standalone\r\n")) 352 | } 353 | return nil 354 | } 355 | 356 | if c.isTxn { 357 | command := Command{cmd: c.cmd, args: c.args} 358 | c.cmds = append(c.cmds, command) 359 | log.Debugf("command:%s added to transaction queue, queue size:%d", c.cmd, len(c.cmds)) 360 | c.rWriter.FlushString("QUEUED") 361 | } else { 362 | c.execute() 363 | } 364 | return nil 365 | } 366 | 367 | func (c *Client) execute() error { 368 | var err error 369 | 370 | start := time.Now() 371 | 372 | if len(c.cmd) == 0 { 373 | err = terror.ErrCommand 374 | } else if f, ok := cmdFind(c.cmd); !ok { 375 | err = terror.ErrCommand 376 | } else { 377 | err = f(c) 378 | } 379 | if err != nil && !c.isTxn { 380 | c.rWriter.FlushError(err) 381 | } 382 | 383 | c.rWriter.Flush() 384 | 385 | log.Debugf("command time cost %d", time.Now().Sub(start).Nanoseconds()) 386 | return err 387 | } 388 | 389 | func (c *Client) SelectDB(dbId uint8) { 390 | c.dbId = dbId 391 | } 392 | 393 | func (c *Client) DBID() uint8 { 394 | return c.dbId 395 | } 396 | -------------------------------------------------------------------------------- /server/command.go: -------------------------------------------------------------------------------- 1 | // 2 | // command.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package server 9 | 10 | type CmdFunc func(c *Client) error 11 | 12 | var cmds map[string]CmdFunc 13 | 14 | func init() { 15 | cmds = make(map[string]CmdFunc, 50) 16 | } 17 | 18 | func cmdRegister(cmdName string, f CmdFunc) { 19 | if _, ok := cmds[cmdName]; ok { 20 | // cmd already exists 21 | return 22 | } 23 | cmds[cmdName] = f 24 | } 25 | 26 | func cmdFind(cmdName string) (CmdFunc, bool) { 27 | cmd, ok := cmds[cmdName] 28 | return cmd, ok 29 | } 30 | -------------------------------------------------------------------------------- /server/command_hash.go: -------------------------------------------------------------------------------- 1 | // 2 | // command_hash.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package server 9 | 10 | import ( 11 | "github.com/yongman/tidis/terror" 12 | ) 13 | 14 | func init() { 15 | cmdRegister("hget", hgetCommand) 16 | cmdRegister("hstrlen", hstrlenCommand) 17 | cmdRegister("hexists", hexistsCommand) 18 | cmdRegister("hlen", hlenCommand) 19 | cmdRegister("hmget", hmgetCommand) 20 | cmdRegister("hdel", hdelCommand) 21 | cmdRegister("hset", hsetCommand) 22 | cmdRegister("hsetnx", hsetnxCommand) 23 | cmdRegister("hmset", hmsetCommand) 24 | cmdRegister("hkeys", hkeysCommand) 25 | cmdRegister("hvals", hvalsCommand) 26 | cmdRegister("hgetall", hgetallCommand) 27 | } 28 | 29 | func hgetCommand(c *Client) error { 30 | if len(c.args) != 2 { 31 | return terror.ErrCmdParams 32 | } 33 | 34 | v, err := c.tdb.Hget(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1]) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | return c.Resp(v) 40 | } 41 | 42 | func hstrlenCommand(c *Client) error { 43 | if len(c.args) != 2 { 44 | return terror.ErrCmdParams 45 | } 46 | 47 | v, err := c.tdb.Hstrlen(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1]) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | return c.Resp(int64(v)) 53 | } 54 | 55 | func hexistsCommand(c *Client) error { 56 | if len(c.args) != 2 { 57 | return terror.ErrCmdParams 58 | } 59 | 60 | v, err := c.tdb.Hexists(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1]) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | if v { 66 | err = c.Resp(int64(1)) 67 | } else { 68 | err = c.Resp(int64(0)) 69 | } 70 | 71 | return err 72 | } 73 | 74 | func hlenCommand(c *Client) error { 75 | if len(c.args) != 1 { 76 | return terror.ErrCmdParams 77 | } 78 | 79 | v, err := c.tdb.Hlen(c.dbId, c.GetCurrentTxn(), c.args[0]) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | return c.Resp(int64(v)) 85 | } 86 | 87 | func hmgetCommand(c *Client) error { 88 | if len(c.args) < 2 { 89 | return terror.ErrCmdParams 90 | } 91 | 92 | v, err := c.tdb.Hmget(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | return c.Resp(v) 98 | } 99 | 100 | func hdelCommand(c *Client) error { 101 | if len(c.args) < 2 { 102 | return terror.ErrCmdParams 103 | } 104 | 105 | var ( 106 | v uint64 107 | err error 108 | ) 109 | 110 | if !c.IsTxn() { 111 | v, err = c.tdb.Hdel(c.dbId, c.args[0], c.args[1:]...) 112 | } else { 113 | v, err = c.tdb.HdelWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 114 | } 115 | if err != nil { 116 | return err 117 | } 118 | 119 | return c.Resp(int64(v)) 120 | } 121 | 122 | func hsetCommand(c *Client) error { 123 | if len(c.args) != 3 { 124 | return terror.ErrCmdParams 125 | } 126 | 127 | var ( 128 | v uint8 129 | err error 130 | ) 131 | 132 | if !c.IsTxn() { 133 | v, err = c.tdb.Hset(c.dbId, c.args[0], c.args[1], c.args[2]) 134 | } else { 135 | v, err = c.tdb.HsetWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1], c.args[2]) 136 | } 137 | if err != nil { 138 | return err 139 | } 140 | 141 | return c.Resp(int64(v)) 142 | } 143 | 144 | func hsetnxCommand(c *Client) error { 145 | if len(c.args) != 3 { 146 | return terror.ErrCmdParams 147 | } 148 | 149 | var ( 150 | v uint8 151 | err error 152 | ) 153 | 154 | if !c.IsTxn() { 155 | v, err = c.tdb.Hsetnx(c.dbId, c.args[0], c.args[1], c.args[2]) 156 | } else { 157 | v, err = c.tdb.HsetnxWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1], c.args[2]) 158 | } 159 | if err != nil { 160 | return err 161 | } 162 | 163 | return c.Resp(int64(v)) 164 | } 165 | 166 | func hmsetCommand(c *Client) error { 167 | if len(c.args) < 3 { 168 | return terror.ErrCmdParams 169 | } 170 | 171 | var err error 172 | 173 | if !c.IsTxn() { 174 | err = c.tdb.Hmset(c.dbId, c.args[0], c.args[1:]...) 175 | } else { 176 | err = c.tdb.HmsetWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 177 | } 178 | if err != nil { 179 | return err 180 | } 181 | 182 | return c.Resp("OK") 183 | } 184 | 185 | func hkeysCommand(c *Client) error { 186 | if len(c.args) != 1 { 187 | return terror.ErrCmdParams 188 | } 189 | 190 | v, err := c.tdb.Hkeys(c.dbId, c.GetCurrentTxn(), c.args[0]) 191 | if err != nil { 192 | return err 193 | } 194 | 195 | return c.Resp(v) 196 | } 197 | 198 | func hvalsCommand(c *Client) error { 199 | if len(c.args) != 1 { 200 | return terror.ErrCmdParams 201 | } 202 | 203 | v, err := c.tdb.Hvals(c.dbId, c.GetCurrentTxn(), c.args[0]) 204 | if err != nil { 205 | return err 206 | } 207 | 208 | return c.Resp(v) 209 | } 210 | 211 | func hgetallCommand(c *Client) error { 212 | if len(c.args) != 1 { 213 | return terror.ErrCmdParams 214 | } 215 | 216 | v, err := c.tdb.Hgetall(c.dbId, c.GetCurrentTxn(), c.args[0]) 217 | if err != nil { 218 | return err 219 | } 220 | 221 | return c.Resp(v) 222 | } 223 | -------------------------------------------------------------------------------- /server/command_list.go: -------------------------------------------------------------------------------- 1 | // 2 | // command_list.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package server 9 | 10 | import ( 11 | "github.com/yongman/go/util" 12 | "github.com/yongman/tidis/terror" 13 | ) 14 | 15 | func init() { 16 | cmdRegister("lpush", lpushCommand) 17 | cmdRegister("lpop", lpopCommand) 18 | cmdRegister("rpush", rpushCommand) 19 | cmdRegister("rpop", rpopCommand) 20 | cmdRegister("llen", llenCommand) 21 | cmdRegister("lindex", lindexCommand) 22 | cmdRegister("lrange", lrangeComamnd) 23 | cmdRegister("lset", lsetCommand) 24 | cmdRegister("ltrim", ltrimCommand) 25 | } 26 | 27 | func lpushCommand(c *Client) error { 28 | if len(c.args) < 2 { 29 | return terror.ErrCmdParams 30 | } 31 | 32 | v, err := c.tdb.Lpush(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return c.Resp(int64(v)) 38 | } 39 | 40 | func lpopCommand(c *Client) error { 41 | if len(c.args) != 1 { 42 | return terror.ErrCmdParams 43 | } 44 | 45 | v, err := c.tdb.Lpop(c.dbId, c.GetCurrentTxn(), c.args[0]) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | return c.Resp(v) 51 | } 52 | 53 | func rpushCommand(c *Client) error { 54 | if len(c.args) < 2 { 55 | return terror.ErrCmdParams 56 | } 57 | 58 | v, err := c.tdb.Rpush(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | return c.Resp(int64(v)) 64 | } 65 | 66 | func rpopCommand(c *Client) error { 67 | if len(c.args) != 1 { 68 | return terror.ErrCmdParams 69 | } 70 | 71 | v, err := c.tdb.Rpop(c.dbId, c.GetCurrentTxn(), c.args[0]) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | return c.Resp(v) 77 | } 78 | 79 | func llenCommand(c *Client) error { 80 | if len(c.args) != 1 { 81 | return terror.ErrCmdParams 82 | } 83 | 84 | v, err := c.tdb.Llen(c.dbId, c.GetCurrentTxn(), c.args[0]) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | return c.Resp(int64(v)) 90 | } 91 | 92 | func lindexCommand(c *Client) error { 93 | if len(c.args) != 2 { 94 | return terror.ErrCmdParams 95 | } 96 | 97 | index, err := util.StrBytesToInt64(c.args[1]) 98 | if err != nil { 99 | return terror.ErrCmdParams 100 | } 101 | v, err := c.tdb.Lindex(c.dbId, c.GetCurrentTxn(), c.args[0], index) 102 | if err != nil { 103 | return err 104 | } 105 | 106 | return c.Resp(v) 107 | } 108 | 109 | func lrangeComamnd(c *Client) error { 110 | if len(c.args) != 3 { 111 | return terror.ErrCmdParams 112 | } 113 | 114 | start, err := util.StrBytesToInt64(c.args[1]) 115 | if err != nil { 116 | return terror.ErrCmdParams 117 | } 118 | 119 | end, err := util.StrBytesToInt64(c.args[2]) 120 | if err != nil { 121 | return terror.ErrCmdParams 122 | } 123 | 124 | v, err := c.tdb.Lrange(c.dbId, c.GetCurrentTxn(), c.args[0], start, end) 125 | if err != nil { 126 | return err 127 | } 128 | 129 | return c.Resp(v) 130 | } 131 | 132 | func lsetCommand(c *Client) error { 133 | if len(c.args) != 3 { 134 | return terror.ErrCmdParams 135 | } 136 | 137 | index, err := util.StrBytesToInt64(c.args[1]) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | if !c.IsTxn() { 143 | err = c.tdb.Lset(c.dbId, c.args[0], index, c.args[2]) 144 | } else { 145 | err = c.tdb.LsetWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], index, c.args[2]) 146 | } 147 | if err != nil { 148 | return err 149 | } 150 | 151 | return c.Resp("OK") 152 | } 153 | 154 | func ltrimCommand(c *Client) error { 155 | if len(c.args) != 3 { 156 | return terror.ErrCmdParams 157 | } 158 | 159 | start, err := util.StrBytesToInt64(c.args[1]) 160 | if err != nil { 161 | return terror.ErrCmdParams 162 | } 163 | 164 | end, err := util.StrBytesToInt64(c.args[2]) 165 | if err != nil { 166 | return terror.ErrCmdParams 167 | } 168 | 169 | if !c.IsTxn() { 170 | err = c.tdb.Ltrim(c.dbId, c.args[0], start, end) 171 | } else { 172 | err = c.tdb.LtrimWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], start, end) 173 | } 174 | if err != nil { 175 | return err 176 | } 177 | 178 | return c.Resp("OK") 179 | } 180 | -------------------------------------------------------------------------------- /server/command_server.go: -------------------------------------------------------------------------------- 1 | // 2 | // command_server.go 3 | // Copyright (C) 2020 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package server 9 | 10 | import ( 11 | "github.com/yongman/tidis/terror" 12 | "strconv" 13 | ) 14 | 15 | func init() { 16 | cmdRegister("flushdb", flushdbCommand) 17 | cmdRegister("flushall", flushallCommand) 18 | cmdRegister("select", selectCommand) 19 | } 20 | 21 | func flushdbCommand(c *Client) error { 22 | err := c.tdb.FlushDB(c.DBID()) 23 | if err != nil { 24 | return err 25 | } 26 | return c.Resp("OK") 27 | } 28 | 29 | func flushallCommand(c *Client) error { 30 | err := c.tdb.FlushAll() 31 | if err != nil { 32 | return err 33 | } 34 | return c.Resp("OK") 35 | } 36 | 37 | func selectCommand(c *Client) error { 38 | if len(c.args) != 1 { 39 | return terror.ErrCmdParams 40 | } 41 | dbId, err := strconv.Atoi(string(c.args[0])) 42 | if err != nil { 43 | return terror.ErrCmdParams 44 | } 45 | c.SelectDB(uint8(dbId)) 46 | return c.Resp("OK") 47 | } -------------------------------------------------------------------------------- /server/command_set.go: -------------------------------------------------------------------------------- 1 | // 2 | // command_set.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package server 9 | 10 | import ( 11 | "github.com/yongman/tidis/terror" 12 | ) 13 | 14 | func init() { 15 | cmdRegister("sadd", saddCommand) 16 | cmdRegister("scard", scardCommand) 17 | cmdRegister("sismember", sismemberCommand) 18 | cmdRegister("smembers", smembersCommand) 19 | cmdRegister("srem", sremCommand) 20 | cmdRegister("sdiff", sdiffCommand) 21 | cmdRegister("sunion", sunionCommand) 22 | cmdRegister("sinter", sinterCommand) 23 | cmdRegister("sdiffstore", sdiffstoreCommand) 24 | cmdRegister("sunionstore", sunionstoreCommand) 25 | cmdRegister("sinterstore", sinterstoreCommand) 26 | cmdRegister("sclear", sclearCommand) 27 | } 28 | 29 | func saddCommand(c *Client) error { 30 | if len(c.args) < 2 { 31 | return terror.ErrCmdParams 32 | } 33 | var ( 34 | v uint64 35 | err error 36 | ) 37 | if !c.IsTxn() { 38 | v, err = c.tdb.Sadd(c.dbId, c.args[0], c.args[1:]...) 39 | } else { 40 | v, err = c.tdb.SaddWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 41 | } 42 | if err != nil { 43 | return err 44 | } 45 | 46 | return c.Resp(int64(v)) 47 | } 48 | 49 | func scardCommand(c *Client) error { 50 | if len(c.args) != 1 { 51 | return terror.ErrCmdParams 52 | } 53 | 54 | v, err := c.tdb.Scard(c.dbId, c.GetCurrentTxn(), c.args[0]) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | return c.Resp(int64(v)) 60 | } 61 | 62 | func sismemberCommand(c *Client) error { 63 | if len(c.args) != 2 { 64 | return terror.ErrCmdParams 65 | } 66 | 67 | v, err := c.tdb.Sismember(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1]) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | return c.Resp(int64(v)) 73 | } 74 | 75 | func smembersCommand(c *Client) error { 76 | if len(c.args) != 1 { 77 | return terror.ErrCmdParams 78 | } 79 | 80 | v, err := c.tdb.Smembers(c.dbId, c.GetCurrentTxn(), c.args[0]) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | return c.Resp(v) 86 | } 87 | 88 | func sremCommand(c *Client) error { 89 | if len(c.args) < 2 { 90 | return terror.ErrCmdParams 91 | } 92 | 93 | var ( 94 | v uint64 95 | err error 96 | ) 97 | if !c.IsTxn() { 98 | v, err = c.tdb.Srem(c.dbId, c.args[0], c.args[1:]...) 99 | } else { 100 | v, err = c.tdb.SremWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 101 | } 102 | if err != nil { 103 | return err 104 | } 105 | 106 | return c.Resp(int64(v)) 107 | } 108 | 109 | func sdiffCommand(c *Client) error { 110 | if len(c.args) < 2 { 111 | return terror.ErrCmdParams 112 | } 113 | 114 | v, err := c.tdb.Sdiff(c.dbId, c.GetCurrentTxn(), c.args...) 115 | if err != nil { 116 | return err 117 | } 118 | 119 | return c.Resp1(v) 120 | } 121 | 122 | func sunionCommand(c *Client) error { 123 | if len(c.args) < 2 { 124 | return terror.ErrCmdParams 125 | } 126 | 127 | v, err := c.tdb.Sunion(c.dbId, c.GetCurrentTxn(), c.args...) 128 | if err != nil { 129 | return err 130 | } 131 | 132 | return c.Resp1(v) 133 | } 134 | 135 | func sinterCommand(c *Client) error { 136 | if len(c.args) < 2 { 137 | return terror.ErrCmdParams 138 | } 139 | 140 | v, err := c.tdb.Sinter(c.dbId, c.GetCurrentTxn(), c.args...) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | return c.Resp1(v) 146 | } 147 | func sdiffstoreCommand(c *Client) error { 148 | if len(c.args) < 2 { 149 | return terror.ErrCmdParams 150 | } 151 | 152 | var ( 153 | v uint64 154 | err error 155 | ) 156 | 157 | if !c.IsTxn() { 158 | v, err = c.tdb.Sdiffstore(c.dbId, c.args[0], c.args[1:]...) 159 | } else { 160 | v, err = c.tdb.SdiffstoreWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 161 | } 162 | if err != nil { 163 | return err 164 | } 165 | 166 | return c.Resp(int64(v)) 167 | } 168 | 169 | func sinterstoreCommand(c *Client) error { 170 | if len(c.args) < 2 { 171 | return terror.ErrCmdParams 172 | } 173 | 174 | var ( 175 | v uint64 176 | err error 177 | ) 178 | 179 | if !c.IsTxn() { 180 | v, err = c.tdb.Sinterstore(c.dbId, c.args[0], c.args[1:]...) 181 | } else { 182 | v, err = c.tdb.SinterstoreWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 183 | } 184 | if err != nil { 185 | return err 186 | } 187 | 188 | return c.Resp(int64(v)) 189 | } 190 | 191 | func sunionstoreCommand(c *Client) error { 192 | if len(c.args) < 2 { 193 | return terror.ErrCmdParams 194 | } 195 | 196 | var ( 197 | v uint64 198 | err error 199 | ) 200 | 201 | if !c.IsTxn() { 202 | v, err = c.tdb.Sunionstore(c.dbId, c.args[0], c.args[1:]...) 203 | } else { 204 | v, err = c.tdb.SunionstoreWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 205 | } 206 | if err != nil { 207 | return err 208 | } 209 | 210 | return c.Resp(int64(v)) 211 | } 212 | 213 | func sclearCommand(c *Client) error { 214 | if len(c.args) < 1 { 215 | return terror.ErrCmdParams 216 | } 217 | 218 | var ( 219 | v uint64 220 | err error 221 | ) 222 | 223 | if !c.IsTxn() { 224 | v, err = c.tdb.Sclear(c.dbId, c.args...) 225 | } else { 226 | v, err = c.tdb.SclearWithTxn(c.dbId, c.GetCurrentTxn(), c.args...) 227 | } 228 | if err != nil { 229 | return err 230 | } 231 | 232 | return c.Resp(int64(v)) 233 | } 234 | -------------------------------------------------------------------------------- /server/command_string.go: -------------------------------------------------------------------------------- 1 | // 2 | // command_string.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package server 9 | 10 | import ( 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/yongman/go/util" 15 | "github.com/yongman/tidis/terror" 16 | ) 17 | 18 | func init() { 19 | cmdRegister("get", getCommand) 20 | cmdRegister("getbit", getBitCommand) 21 | cmdRegister("set", setCommand) 22 | cmdRegister("setbit", setBitCommand) 23 | cmdRegister("bitcount", bitCountCommand) 24 | cmdRegister("setex", setexCommand) 25 | cmdRegister("del", delCommand) 26 | cmdRegister("mget", mgetCommand) 27 | cmdRegister("mset", msetCommand) 28 | cmdRegister("incr", incrCommand) 29 | cmdRegister("incrby", incrbyCommand) 30 | cmdRegister("decr", decrCommand) 31 | cmdRegister("decrby", decrbyCommand) 32 | cmdRegister("strlen", strlenCommand) 33 | cmdRegister("pexpire", pexpireCommand) 34 | cmdRegister("pexpireat", pexpireatCommand) 35 | cmdRegister("expire", expireCommand) 36 | cmdRegister("expireat", expireatCommand) 37 | cmdRegister("pttl", pttlCommand) 38 | cmdRegister("ttl", ttlCommand) 39 | cmdRegister("type", typeCommand) 40 | } 41 | 42 | func getCommand(c *Client) error { 43 | if len(c.args) != 1 { 44 | return terror.ErrCmdParams 45 | } 46 | var ( 47 | v []byte 48 | err error 49 | ) 50 | v, err = c.tdb.Get(c.dbId, c.GetCurrentTxn(), c.args[0]) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | return c.Resp(v) 56 | } 57 | 58 | func getBitCommand(c *Client) error { 59 | if len(c.args) != 2 { 60 | return terror.ErrCmdParams 61 | } else if c.args[1][0] == '-' { 62 | return terror.ErrCmdParams 63 | } 64 | 65 | var ( 66 | v []byte 67 | vRet byte 68 | err error 69 | bitsCnt int 70 | bitPos int 71 | bytesCnt int 72 | ) 73 | 74 | bitPos, err = strconv.Atoi(string(c.args[1])) 75 | if err != nil { 76 | return terror.ErrCmdParams 77 | } 78 | bitsCnt = bitPos + 1 79 | 80 | if bitsCnt%8 == 0 { 81 | bytesCnt = bitsCnt / 8 82 | } else { 83 | bytesCnt = (bitsCnt / 8) + 1 84 | } 85 | 86 | v, err = c.tdb.Get(c.dbId, c.GetCurrentTxn(), c.args[0]) 87 | if err != nil { 88 | return err 89 | } else if v == nil { 90 | // the key is not exist yet, return zero. 91 | vRet = 0 92 | } else { 93 | // get the key, then change its value 94 | if bitsCnt <= len(v)*8 { 95 | // if get bit pos is less than or equal to it's length. 96 | // get bit operation 97 | vRet = (v[bytesCnt-1] >> (uint)(bitPos%8)) & 1 98 | } else { 99 | // if get bit pos is bigger than it's length, return zero. 100 | vRet = 0 101 | } 102 | } 103 | 104 | return c.Resp(int64(vRet)) 105 | } 106 | 107 | func bitCountCommand(c *Client) error { 108 | if len(c.args) != 1 { 109 | return terror.ErrCmdParams 110 | } 111 | 112 | var ( 113 | i int 114 | v []byte 115 | x uint8 116 | err error 117 | bitsOneCnt int 118 | bytesCnt int 119 | ) 120 | 121 | v, err = c.tdb.Get(c.dbId, c.GetCurrentTxn(), c.args[0]) 122 | if err != nil { 123 | return err 124 | } else if v == nil { 125 | // the key is not exist yet, return zero. 126 | bitsOneCnt = 0 127 | } else { 128 | // get the key, then calculate the one value bits count 129 | bitsOneCnt = 0 130 | bytesCnt = len(v) 131 | for i = 0; i < bytesCnt; i++ { 132 | x = v[i] 133 | for ; x > 0; bitsOneCnt++ { 134 | x &= x - 1 135 | } 136 | } 137 | } 138 | 139 | return c.Resp(int64(bitsOneCnt)) 140 | } 141 | 142 | func mgetCommand(c *Client) error { 143 | if len(c.args) < 1 { 144 | return terror.ErrCmdParams 145 | } 146 | 147 | var ( 148 | ret []interface{} 149 | err error 150 | ) 151 | 152 | ret, err = c.tdb.MGet(c.dbId, c.GetCurrentTxn(), c.args) 153 | if err != nil { 154 | return err 155 | } 156 | 157 | return c.Resp(ret) 158 | } 159 | 160 | func setCommand(c *Client) error { 161 | if len(c.args) < 2 || len(c.args) > 5 { 162 | return terror.ErrCmdParams 163 | } 164 | //SET key value 165 | if len(c.args) == 2 { 166 | err := c.tdb.Set(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1]) 167 | if err != nil { 168 | return err 169 | } 170 | } 171 | 172 | if len(c.args) >= 3 { 173 | ttlMs := uint64(0) 174 | nxFlag := false 175 | xxFlag := false 176 | ttlFlag := false 177 | var err error 178 | 179 | i := 2 180 | for i < len(c.args) { 181 | commandItem := strings.ToLower(string(c.args[i])) 182 | if commandItem == "nx" { 183 | nxFlag = true 184 | } else if commandItem == "xx" { 185 | xxFlag = true 186 | } else if commandItem == "ex" { 187 | //get px param 188 | if ttlFlag == true { 189 | return terror.ErrCmdParams 190 | } 191 | 192 | i++ 193 | if i < len(c.args) { 194 | ttlMs, err = util.StrBytesToUint64(c.args[i]) 195 | if err != nil { 196 | return terror.ErrCmdParams 197 | } 198 | ttlMs *= 1000 199 | ttlFlag = true 200 | } else { 201 | return terror.ErrCmdParams 202 | } 203 | } else if commandItem == "px" { 204 | //get px param 205 | if ttlFlag == true { 206 | return terror.ErrCmdParams 207 | } 208 | i++ 209 | if i < len(c.args) { 210 | ttlMs, err = util.StrBytesToUint64(c.args[i]) 211 | if err != nil { 212 | return terror.ErrCmdParams 213 | } 214 | } else { 215 | return terror.ErrCmdParams 216 | } 217 | } 218 | i++ 219 | } 220 | 221 | //Can not set nx and xx at sametime 222 | if nxFlag == true && xxFlag == true { 223 | return terror.ErrCmdParams 224 | } 225 | 226 | var result bool 227 | result, err = c.tdb.SetWithParam(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1], ttlMs, nxFlag, xxFlag) 228 | if err != nil { 229 | return err 230 | } 231 | 232 | if result == false { 233 | return c.Resp(nil) 234 | } 235 | } 236 | return c.Resp("OK") 237 | 238 | } 239 | 240 | func setBitCommand(c *Client) error { 241 | if len(c.args) != 3 { 242 | return terror.ErrCmdParams 243 | } else if c.args[1][0] == '-' { 244 | return terror.ErrCmdParams 245 | } else if (len(c.args[2]) != 1) || (c.args[2][0] != '0' && c.args[2][0] != '1') { 246 | return terror.ErrCmdParams 247 | } 248 | 249 | var ( 250 | i int 251 | v []byte 252 | err error 253 | bitsCnt int 254 | bitPos int 255 | bytesCnt int 256 | ) 257 | 258 | bitPos, err = strconv.Atoi(string(c.args[1])) 259 | if err != nil || (bitPos+1) > 1*1024*1024*8 { 260 | return terror.ErrCmdParams 261 | } 262 | bitsCnt = bitPos + 1 263 | 264 | // offset starts with 0, we need to +1 to do calculation 265 | if bitsCnt%8 == 0 { 266 | bytesCnt = bitsCnt / 8 267 | } else { 268 | bytesCnt = (bitsCnt / 8) + 1 269 | } 270 | 271 | v, err = c.tdb.Get(c.dbId, c.GetCurrentTxn(), c.args[0]) 272 | if err != nil { 273 | return err 274 | } else if v == nil { 275 | // the key is not exist yet, we should create it. 276 | v = make([]byte, bytesCnt) 277 | 278 | // init all the bits with zero 279 | for i := 0; i < bytesCnt; i++ { 280 | v[i] = 0 281 | } 282 | 283 | // set bit 0,1 operation 284 | if c.args[2][0] == '0' { 285 | v[bytesCnt-1] &= ^(1 << (uint)(bitPos%8)) 286 | } else if c.args[2][0] == '1' { 287 | v[bytesCnt-1] |= (1 << (uint)(bitPos%8)) 288 | } 289 | } else { 290 | // get the key, then change its value 291 | if bitsCnt <= len(v)*8 { 292 | // if set bit pos is less than or equal to it's length, just set it 293 | // set bit 0,1 operation 294 | if c.args[2][0] == '0' { 295 | v[bytesCnt-1] &= ^(1 << (uint)(bitPos%8)) 296 | } else if c.args[2][0] == '1' { 297 | v[bytesCnt-1] |= (1 << (uint)(bitPos%8)) 298 | } 299 | } else { 300 | // if set bit pos is bigger than it's length, append it and then chagne it 301 | j := bytesCnt - len(v) 302 | for i = 0; i < j; i++ { 303 | v = append(v, 0) 304 | } 305 | // set bit 0,1 operation 306 | if c.args[2][0] == '0' { 307 | v[bytesCnt-1] &= ^(1 << (uint)(bitPos%8)) 308 | } else if c.args[2][0] == '1' { 309 | v[bytesCnt-1] |= (1 << (uint)(bitPos%8)) 310 | } 311 | } 312 | } 313 | 314 | err = c.tdb.Set(c.dbId, c.GetCurrentTxn(), c.args[0], v) 315 | if err != nil { 316 | return err 317 | } 318 | if c.args[2][0] == '0' { 319 | return c.Resp(int64(1)) 320 | } 321 | return c.Resp(int64(0)) 322 | } 323 | 324 | func setexCommand(c *Client) error { 325 | if len(c.args) != 3 { 326 | return terror.ErrCmdParams 327 | } 328 | 329 | var ( 330 | err error 331 | sec int64 332 | ) 333 | 334 | sec, err = util.StrBytesToInt64(c.args[1]) 335 | if err != nil { 336 | return terror.ErrCmdParams 337 | } 338 | 339 | if !c.IsTxn() { 340 | err = c.tdb.Setex(c.dbId, c.args[0], sec, c.args[2]) 341 | } else { 342 | err = c.tdb.SetexWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], sec, c.args[2]) 343 | } 344 | if err != nil { 345 | return err 346 | } 347 | 348 | return c.Resp("OK") 349 | } 350 | 351 | func msetCommand(c *Client) error { 352 | if len(c.args) < 2 && len(c.args)%2 != 0 { 353 | return terror.ErrCmdParams 354 | } 355 | 356 | _, err := c.tdb.MSet(c.dbId, c.GetCurrentTxn(), c.args) 357 | if err != nil { 358 | return err 359 | } 360 | 361 | return c.Resp("OK") 362 | } 363 | 364 | func delCommand(c *Client) error { 365 | if len(c.args) < 1 { 366 | return terror.ErrCmdParams 367 | } 368 | 369 | ret, err := c.tdb.Delete(c.dbId, c.GetCurrentTxn(), c.args) 370 | if err != nil { 371 | return err 372 | } 373 | 374 | return c.Resp(int64(ret)) 375 | } 376 | 377 | func incrCommand(c *Client) error { 378 | if len(c.args) != 1 { 379 | return terror.ErrCmdParams 380 | } 381 | 382 | var ( 383 | ret int64 384 | err error 385 | ) 386 | 387 | if !c.IsTxn() { 388 | ret, err = c.tdb.Incr(c.dbId, c.args[0], 1) 389 | } else { 390 | ret, err = c.tdb.IncrWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], 1) 391 | } 392 | if err != nil { 393 | return err 394 | } 395 | 396 | return c.Resp(ret) 397 | } 398 | 399 | func incrbyCommand(c *Client) error { 400 | if len(c.args) != 2 { 401 | return terror.ErrCmdParams 402 | } 403 | 404 | var ( 405 | step int64 406 | err error 407 | ret int64 408 | ) 409 | 410 | step, err = util.StrBytesToInt64(c.args[1]) 411 | if err != nil { 412 | return terror.ErrCmdParams 413 | } 414 | 415 | if !c.IsTxn() { 416 | ret, err = c.tdb.Incr(c.dbId, c.args[0], step) 417 | } else { 418 | ret, err = c.tdb.IncrWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], step) 419 | } 420 | if err != nil { 421 | return err 422 | } 423 | 424 | return c.Resp(ret) 425 | } 426 | 427 | func decrCommand(c *Client) error { 428 | if len(c.args) != 1 { 429 | return terror.ErrCmdParams 430 | } 431 | 432 | var ( 433 | ret int64 434 | err error 435 | ) 436 | 437 | if !c.IsTxn() { 438 | ret, err = c.tdb.Decr(c.dbId, c.args[0], 1) 439 | } else { 440 | ret, err = c.tdb.DecrWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], 1) 441 | } 442 | 443 | if err != nil { 444 | return err 445 | } 446 | 447 | return c.Resp(ret) 448 | } 449 | 450 | func decrbyCommand(c *Client) error { 451 | if len(c.args) != 2 { 452 | return terror.ErrCmdParams 453 | } 454 | 455 | var ( 456 | step int64 457 | err error 458 | ret int64 459 | ) 460 | 461 | step, err = util.StrBytesToInt64(c.args[1]) 462 | if err != nil { 463 | return terror.ErrCmdParams 464 | } 465 | 466 | if !c.IsTxn() { 467 | ret, err = c.tdb.Decr(c.dbId, c.args[0], step) 468 | } else { 469 | ret, err = c.tdb.DecrWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], step) 470 | } 471 | if err != nil { 472 | return err 473 | } 474 | 475 | return c.Resp(ret) 476 | } 477 | 478 | func strlenCommand(c *Client) error { 479 | if len(c.args) != 1 { 480 | return terror.ErrCmdParams 481 | } 482 | 483 | var ( 484 | v []byte 485 | err error 486 | ) 487 | 488 | v, err = c.tdb.Get(c.dbId, c.GetCurrentTxn(), c.args[0]) 489 | if err != nil { 490 | return err 491 | } 492 | 493 | return c.Resp(int64(len(v))) 494 | } 495 | 496 | func pexpireCommand(c *Client) error { 497 | var ( 498 | v int 499 | err error 500 | ) 501 | 502 | i, err := util.StrBytesToInt64(c.args[1]) 503 | if err != nil { 504 | return terror.ErrCmdParams 505 | } 506 | if !c.IsTxn() { 507 | v, err = c.tdb.PExpire(c.DBID(), c.args[0], i) 508 | } else { 509 | v, err = c.tdb.PExpireWithTxn(c.DBID(), c.GetCurrentTxn(), c.args[0], i) 510 | } 511 | if err != nil { 512 | return err 513 | } 514 | return c.Resp(int64(v)) 515 | } 516 | 517 | func pexpireatCommand(c *Client) error { 518 | var ( 519 | v int 520 | err error 521 | ) 522 | 523 | i, err := util.StrBytesToInt64(c.args[1]) 524 | if err != nil { 525 | return terror.ErrCmdParams 526 | } 527 | if !c.IsTxn() { 528 | v, err = c.tdb.PExpireAt(c.DBID(), c.args[0], i) 529 | } else { 530 | v, err = c.tdb.PExpireAtWithTxn(c.DBID(), c.GetCurrentTxn(), c.args[0], i) 531 | } 532 | if err != nil { 533 | return err 534 | } 535 | return c.Resp(int64(v)) 536 | } 537 | 538 | func expireCommand(c *Client) error { 539 | var ( 540 | v int 541 | err error 542 | ) 543 | 544 | i, err := util.StrBytesToInt64(c.args[1]) 545 | if err != nil { 546 | return terror.ErrCmdParams 547 | } 548 | if !c.IsTxn() { 549 | v, err = c.tdb.Expire(c.DBID(), c.args[0], i) 550 | } else { 551 | v, err = c.tdb.ExpireWithTxn(c.DBID(), c.GetCurrentTxn(), c.args[0], i) 552 | } 553 | if err != nil { 554 | return err 555 | } 556 | return c.Resp(int64(v)) 557 | } 558 | 559 | func expireatCommand(c *Client) error { 560 | var ( 561 | v int 562 | err error 563 | ) 564 | 565 | i, err := util.StrBytesToInt64(c.args[1]) 566 | if err != nil { 567 | return terror.ErrCmdParams 568 | } 569 | if !c.IsTxn() { 570 | v, err = c.tdb.ExpireAt(c.DBID(), c.args[0], i) 571 | } else { 572 | v, err = c.tdb.ExpireAtWithTxn(c.DBID(), c.GetCurrentTxn(), c.args[0], i) 573 | } 574 | if err != nil { 575 | return err 576 | } 577 | return c.Resp(int64(v)) 578 | } 579 | 580 | func pttlCommand(c *Client) error { 581 | var ( 582 | v int64 583 | err error 584 | ) 585 | v, err = c.tdb.PTtl(c.DBID(), c.GetCurrentTxn(), c.args[0]) 586 | if err != nil { 587 | return err 588 | } 589 | return c.Resp(v) 590 | } 591 | 592 | func ttlCommand(c *Client) error { 593 | var ( 594 | v int64 595 | err error 596 | ) 597 | v, err = c.tdb.Ttl(c.DBID(), c.GetCurrentTxn(), c.args[0]) 598 | if err != nil { 599 | return err 600 | } 601 | return c.Resp(v) 602 | } 603 | 604 | func typeCommand(c *Client) error { 605 | var ( 606 | ret string 607 | err error 608 | ) 609 | ret, err = c.tdb.Type(c.DBID(), c.GetCurrentTxn(), c.args[0]) 610 | if err != nil { 611 | return err 612 | } 613 | return c.Resp(ret) 614 | } -------------------------------------------------------------------------------- /server/command_zset.go: -------------------------------------------------------------------------------- 1 | // 2 | // command_zset.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package server 9 | 10 | import ( 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/yongman/go/util" 15 | "github.com/yongman/tidis/terror" 16 | "github.com/yongman/tidis/tidis" 17 | ) 18 | 19 | func init() { 20 | cmdRegister("zadd", zaddCommand) 21 | cmdRegister("zcard", zcardCommand) 22 | cmdRegister("zrange", zrangeCommand) 23 | cmdRegister("zrevrange", zrevrangeCommand) 24 | cmdRegister("zrangebyscore", zrangebyscoreCommand) 25 | cmdRegister("zrevrangebyscore", zrevrangebyscoreCommand) 26 | cmdRegister("zremrangebyscore", zremrangebyscoreCommand) 27 | cmdRegister("zrangebylex", zrangebylexCommand) 28 | cmdRegister("zrevrangebylex", zrevrangebylexCommand) 29 | cmdRegister("zremrangebylex", zremrangebylexCommand) 30 | cmdRegister("zcount", zcountCommand) 31 | cmdRegister("zlexcount", zlexcountCommand) 32 | cmdRegister("zscore", zscoreCommand) 33 | cmdRegister("zrem", zremCommand) 34 | cmdRegister("zincrby", zincrbyCommand) 35 | cmdRegister("zrank", zrankCommand) 36 | cmdRegister("zrevrank", zrevrankCommand) 37 | } 38 | 39 | func zaddCommand(c *Client) error { 40 | if len(c.args) < 3 && len(c.args)%2 == 0 { 41 | return terror.ErrCmdParams 42 | } 43 | 44 | mps := make([]*tidis.MemberPair, 0) 45 | 46 | for i := 1; i < len(c.args); i += 2 { 47 | score, err := util.StrBytesToInt64(c.args[i]) 48 | if err != nil { 49 | return err 50 | } 51 | mp := &tidis.MemberPair{ 52 | Score: score, 53 | Member: c.args[i+1], 54 | } 55 | mps = append(mps, mp) 56 | } 57 | 58 | var ( 59 | v int 60 | err error 61 | ) 62 | 63 | if !c.IsTxn() { 64 | v, err = c.tdb.Zadd(c.dbId, c.args[0], mps...) 65 | } else { 66 | v, err = c.tdb.ZaddWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], mps...) 67 | } 68 | if err != nil { 69 | return err 70 | } 71 | 72 | return c.Resp(int64(v)) 73 | } 74 | 75 | func zcardCommand(c *Client) error { 76 | if len(c.args) != 1 { 77 | return terror.ErrCmdParams 78 | } 79 | 80 | v, err := c.tdb.Zcard(c.dbId, c.GetCurrentTxn(), c.args[0]) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | return c.Resp(int64(v)) 86 | } 87 | 88 | func zrangeCommand(c *Client) error { 89 | return zrangeGeneric(c, false) 90 | } 91 | 92 | func zrevrangeCommand(c *Client) error { 93 | return zrangeGeneric(c, true) 94 | } 95 | 96 | func zrangeGeneric(c *Client, reverse bool) error { 97 | if len(c.args) < 3 { 98 | return terror.ErrCmdParams 99 | } 100 | var ( 101 | start, end int64 102 | err error 103 | withscores bool 104 | ) 105 | if len(c.args) == 4 { 106 | str := strings.ToLower(string(c.args[3])) 107 | if str == "withscores" { 108 | withscores = true 109 | } else { 110 | return terror.ErrCmdParams 111 | } 112 | } 113 | 114 | start, err = util.StrBytesToInt64(c.args[1]) 115 | if err != nil { 116 | return err 117 | } 118 | end, err = util.StrBytesToInt64(c.args[2]) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | v, err := c.tdb.Zrange(c.dbId, c.GetCurrentTxn(), c.args[0], start, end, withscores, reverse) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | return c.Resp(v) 129 | } 130 | 131 | func zrangebyscoreCommand(c *Client) error { 132 | return zrangebyscoreGeneric(c, false) 133 | } 134 | 135 | func zrevrangebyscoreCommand(c *Client) error { 136 | return zrangebyscoreGeneric(c, true) 137 | } 138 | 139 | func zrangebyscoreGeneric(c *Client, reverse bool) error { 140 | if len(c.args) < 3 { 141 | return terror.ErrCmdParams 142 | } 143 | 144 | var ( 145 | start, end int64 146 | err error 147 | withscores bool 148 | offset int = -1 149 | count int = -1 150 | ) 151 | 152 | for i := 3; i < len(c.args); i++ { 153 | str := strings.ToLower(string(c.args[i])) 154 | if str == "withscores" { 155 | withscores = true 156 | } else if str == "limit" { 157 | if len(c.args) <= i+2 { 158 | return terror.ErrCmdParams 159 | } 160 | of, err := util.StrBytesToInt64(c.args[i+1]) 161 | if err != nil { 162 | return err 163 | } 164 | offset = int(of) 165 | 166 | co, err := util.StrBytesToInt64(c.args[i+2]) 167 | if err != nil { 168 | return err 169 | } 170 | count = int(co) 171 | break 172 | } 173 | } 174 | 175 | // score pre-process 176 | strScore := strings.ToLower(string(c.args[1])) 177 | switch strScore { 178 | case "-inf": 179 | start = tidis.ScoreMin 180 | case "+inf": 181 | start = tidis.ScoreMax 182 | default: 183 | start, err = util.StrBytesToInt64(c.args[1]) 184 | if err != nil { 185 | return err 186 | } 187 | } 188 | 189 | strScore = strings.ToLower(string(c.args[2])) 190 | switch strScore { 191 | case "-inf": 192 | end = tidis.ScoreMin 193 | case "+inf": 194 | end = tidis.ScoreMax 195 | default: 196 | end, err = util.StrBytesToInt64(c.args[2]) 197 | if err != nil { 198 | return err 199 | } 200 | } 201 | 202 | v, err := c.tdb.Zrangebyscore(c.dbId, c.GetCurrentTxn(), c.args[0], start, end, withscores, offset, count, reverse) 203 | if err != nil { 204 | return err 205 | } 206 | 207 | return c.Resp(v) 208 | } 209 | 210 | func zremrangebyscoreCommand(c *Client) error { 211 | if len(c.args) < 3 { 212 | return terror.ErrCmdParams 213 | } 214 | 215 | var ( 216 | start int64 217 | end int64 218 | v uint64 219 | err error 220 | ) 221 | 222 | // score pre-process 223 | strScore := strings.ToLower(string(c.args[1])) 224 | switch strScore { 225 | case "-inf": 226 | start = tidis.ScoreMin 227 | case "+inf": 228 | start = tidis.ScoreMax 229 | default: 230 | start, err = util.StrBytesToInt64(c.args[1]) 231 | if err != nil { 232 | return err 233 | } 234 | } 235 | 236 | strScore = strings.ToLower(string(c.args[2])) 237 | switch strScore { 238 | case "-inf": 239 | end = tidis.ScoreMin 240 | case "+inf": 241 | end = tidis.ScoreMax 242 | default: 243 | end, err = util.StrBytesToInt64(c.args[2]) 244 | if err != nil { 245 | return err 246 | } 247 | } 248 | 249 | if !c.IsTxn() { 250 | v, err = c.tdb.Zremrangebyscore(c.dbId, c.args[0], start, end) 251 | } else { 252 | v, err = c.tdb.ZremrangebyscoreWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], start, end) 253 | } 254 | if err != nil { 255 | return err 256 | } 257 | 258 | return c.Resp(int64(v)) 259 | } 260 | 261 | func zrangebylexGeneric(c *Client, reverse bool) error { 262 | if len(c.args) < 3 { 263 | return terror.ErrCmdParams 264 | } 265 | 266 | var offset, count int64 = 0, -1 267 | var err error 268 | 269 | if len(c.args) > 3 { 270 | if len(c.args) != 6 { 271 | return terror.ErrCmdParams 272 | } 273 | if strings.ToLower(string(c.args[3])) != "limit" { 274 | return terror.ErrCmdParams 275 | } 276 | offset, err = util.StrBytesToInt64(c.args[4]) 277 | if err != nil { 278 | return err 279 | } 280 | count, err = util.StrBytesToInt64(c.args[5]) 281 | if err != nil { 282 | return err 283 | } 284 | if offset < 0 || count < 0 { 285 | return terror.ErrCmdParams 286 | } 287 | } 288 | 289 | v, err := c.tdb.Zrangebylex(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1], c.args[2], int(offset), int(count), reverse) 290 | if err != nil { 291 | return err 292 | } 293 | 294 | return c.Resp(v) 295 | } 296 | 297 | func zrangebylexCommand(c *Client) error { 298 | return zrangebylexGeneric(c, false) 299 | } 300 | 301 | func zrevrangebylexCommand(c *Client) error { 302 | return zrangebylexGeneric(c, true) 303 | } 304 | 305 | func zremrangebylexCommand(c *Client) error { 306 | if len(c.args) < 3 { 307 | return terror.ErrCmdParams 308 | } 309 | 310 | var ( 311 | v uint64 312 | err error 313 | ) 314 | 315 | if !c.IsTxn() { 316 | v, err = c.tdb.Zremrangebylex(c.dbId, c.args[0], c.args[1], c.args[2]) 317 | } else { 318 | v, err = c.tdb.ZremrangebylexWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1], c.args[2]) 319 | } 320 | if err != nil { 321 | return err 322 | } 323 | 324 | return c.Resp(int64(v)) 325 | } 326 | 327 | func zcountCommand(c *Client) error { 328 | if len(c.args) < 3 { 329 | return terror.ErrCmdParams 330 | } 331 | var min, max int64 332 | var err error 333 | 334 | // score pre-process 335 | strScore := strings.ToLower(string(c.args[1])) 336 | switch strScore { 337 | case "-inf": 338 | min = tidis.ScoreMin 339 | case "+inf": 340 | min = tidis.ScoreMax 341 | default: 342 | min, err = util.StrBytesToInt64(c.args[1]) 343 | if err != nil { 344 | return err 345 | } 346 | } 347 | 348 | strScore = strings.ToLower(string(c.args[2])) 349 | switch strScore { 350 | case "-inf": 351 | max = tidis.ScoreMin 352 | case "+inf": 353 | max = tidis.ScoreMax 354 | default: 355 | max, err = util.StrBytesToInt64(c.args[2]) 356 | if err != nil { 357 | return err 358 | } 359 | } 360 | 361 | v, err := c.tdb.Zcount(c.dbId, c.GetCurrentTxn(), c.args[0], min, max) 362 | if err != nil { 363 | return err 364 | } 365 | 366 | return c.Resp(int64(v)) 367 | } 368 | 369 | func zlexcountCommand(c *Client) error { 370 | if len(c.args) != 3 { 371 | return terror.ErrCmdParams 372 | } 373 | 374 | v, err := c.tdb.Zlexcount(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1], c.args[2]) 375 | if err != nil { 376 | return err 377 | } 378 | 379 | return c.Resp(int64(v)) 380 | } 381 | 382 | func zscoreCommand(c *Client) error { 383 | if len(c.args) != 2 { 384 | return terror.ErrCmdParams 385 | } 386 | 387 | v, exist, err := c.tdb.Zscore(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1]) 388 | if err != nil { 389 | return err 390 | } 391 | 392 | if exist { 393 | str := strconv.AppendInt([]byte(nil), v, 10) 394 | return c.Resp(str) 395 | } else { 396 | return c.Resp([]byte(nil)) 397 | } 398 | } 399 | 400 | func zremCommand(c *Client) error { 401 | if len(c.args) < 2 { 402 | return terror.ErrCmdParams 403 | } 404 | 405 | var ( 406 | v uint64 407 | err error 408 | ) 409 | 410 | if !c.IsTxn() { 411 | v, err = c.tdb.Zrem(c.dbId, c.args[0], c.args[1:]...) 412 | } else { 413 | v, err = c.tdb.ZremWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1:]...) 414 | } 415 | if err != nil { 416 | return err 417 | } 418 | 419 | return c.Resp(int64(v)) 420 | } 421 | 422 | func zincrbyCommand(c *Client) error { 423 | if len(c.args) != 3 { 424 | return terror.ErrCmdParams 425 | } 426 | 427 | delta, err := util.StrBytesToInt64(c.args[1]) 428 | if err != nil { 429 | return err 430 | } 431 | 432 | var v int64 433 | 434 | if !c.IsTxn() { 435 | v, err = c.tdb.Zincrby(c.dbId, c.args[0], delta, c.args[2]) 436 | } else { 437 | v, err = c.tdb.ZincrbyWithTxn(c.dbId, c.GetCurrentTxn(), c.args[0], delta, c.args[2]) 438 | } 439 | if err != nil { 440 | return err 441 | } 442 | 443 | return c.Resp(v) 444 | } 445 | 446 | func zrankCommand(c *Client) error { 447 | if len(c.args) != 2 { 448 | return terror.ErrCmdParams 449 | } 450 | 451 | // 1. check the member exist or not 452 | score, exist, err := c.tdb.Zscore(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1]) 453 | if err != nil { 454 | return err 455 | } 456 | if !exist { 457 | // not exist, just return nil 458 | return c.Resp([]byte(nil)) 459 | } 460 | 461 | // 2. calc the rank 462 | v, exist, err := c.tdb.Zrank(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1], score) 463 | if err != nil { 464 | return err 465 | } 466 | 467 | if exist { 468 | str := strconv.AppendInt([]byte(nil), v, 10) 469 | return c.Resp(str) 470 | } else { 471 | return c.Resp([]byte(nil)) 472 | } 473 | } 474 | 475 | func zrevrankCommand(c *Client) error { 476 | if len(c.args) != 2 { 477 | return terror.ErrCmdParams 478 | } 479 | 480 | // 1. check the member exist or not 481 | score, exist, err := c.tdb.Zscore(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1]) 482 | if err != nil { 483 | return err 484 | } 485 | if !exist { 486 | // not exist, just return nil 487 | return c.Resp([]byte(nil)) 488 | } 489 | 490 | // 2. calc the zset count 491 | count, err := c.tdb.Zcard(c.dbId, c.GetCurrentTxn(), c.args[0]) 492 | if err != nil { 493 | return err 494 | } 495 | 496 | // 3. calc the rank 497 | v, exist, err := c.tdb.Zrank(c.dbId, c.GetCurrentTxn(), c.args[0], c.args[1], score) 498 | if err != nil { 499 | return err 500 | } 501 | 502 | if exist { 503 | r := int64(count) - 1 - v 504 | str := strconv.AppendInt([]byte(nil), r, 10) 505 | return c.Resp(str) 506 | } else { 507 | return c.Resp([]byte(nil)) 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /store/db.go: -------------------------------------------------------------------------------- 1 | // 2 | // db.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package store 9 | 10 | // backend db interface 11 | type DB interface { 12 | Close() error 13 | Get(key []byte) ([]byte, error) 14 | GetWithTxn(key []byte, txn1 interface{}) ([]byte, error) 15 | GetWithSnapshot(key []byte, ss interface{}) ([]byte, error) 16 | GetNewestSnapshot() (interface{}, error) 17 | GetSnapshotFromTxn(txn interface{}) interface{} 18 | GetWithVersion(key []byte, version uint64) ([]byte, error) 19 | MGet(key [][]byte) (map[string][]byte, error) 20 | MGetWithVersion(key [][]byte, version uint64) (map[string][]byte, error) 21 | MGetWithSnapshot(keys [][]byte, ss interface{}) (map[string][]byte, error) 22 | MGetWithTxn(keys [][]byte, txn1 interface{}) (map[string][]byte, error) 23 | Set(key []byte, value []byte) error 24 | SetWithTxn(key []byte, value []byte, txn interface{}) error 25 | MSet(kv map[string][]byte) (int, error) 26 | MSetWithTxn(kvm map[string][]byte, txn interface{}) (int, error) 27 | Delete(keys [][]byte) (int, error) 28 | DeleteWithTxn(keys [][]byte, txn interface{}) (int, error) 29 | BatchInTxn(f func(txn interface{}) (interface{}, error)) (interface{}, error) 30 | GetRangeKeysWithFrontier(start []byte, withstart bool, end []byte, withend bool, offset, limit uint64, snapshot interface{}) ([][]byte, error) 31 | GetRangeKeysWithFrontierWithTxn(start []byte, withstart bool, end []byte, withend bool, offset, limit uint64, txn interface{}) ([][]byte, error) 32 | GetRangeKeys(start []byte, end []byte, offset, limit uint64, snapshot interface{}) ([][]byte, error) 33 | GetRangeKeysWithTxn(start []byte, end []byte, offset, limit uint64, txn interface{}) ([][]byte, error) 34 | GetRangeKeysCount(start []byte, withstart bool, end []byte, withend bool, limit uint64, snapshot interface{}) (uint64, error) 35 | GetRangeKeysCountWithTxn(start []byte, withstart bool, end []byte, withend bool, limit uint64, txn interface{}) (uint64, error) 36 | GetRangeVals(start []byte, end []byte, limit uint64, snapshot interface{}) ([][]byte, error) 37 | GetRangeValsWithTxn(start []byte, end []byte, limit uint64, txn1 interface{}) ([][]byte, error) 38 | GetRangeKeysVals(start []byte, end []byte, limit uint64, snapshot interface{}) ([][]byte, error) 39 | GetRangeKeysValsWithTxn(start []byte, end []byte, limit uint64, txn1 interface{}) ([][]byte, error) 40 | DeleteRange(start []byte, end []byte, limit uint64) (uint64, error) 41 | DeleteRangeWithTxn(start []byte, end []byte, limit uint64, txn1 interface{}) (uint64, error) 42 | GetRank(start, end, obj []byte, snapshot interface{}) (int64, bool, error) 43 | GetRankWithTxn(start, end, obj []byte, txn interface{}) (int64, bool, error) 44 | 45 | BatchWithTxn(f func(txn interface{}) (interface{}, error), txn1 interface{}) (interface{}, error) 46 | NewTxn() (interface{}, error) 47 | 48 | UnsafeDeleteRange(start, end []byte) error 49 | RunGC(safePoint uint64, concurrency int) error 50 | GetCurrentVersion() (uint64, error) 51 | } 52 | 53 | // iterator for backend store 54 | type Iterator interface { 55 | Valid() bool 56 | Key() []byte 57 | Value() []byte 58 | Next() error 59 | Close() 60 | } 61 | -------------------------------------------------------------------------------- /store/store.go: -------------------------------------------------------------------------------- 1 | // 2 | // store.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package store 9 | 10 | import ( 11 | "github.com/yongman/tidis/config" 12 | "github.com/yongman/tidis/store/tikv" 13 | ) 14 | 15 | func init() { 16 | } 17 | 18 | func Open(conf *config.Config) (DB, error) { 19 | db, err := tikv.Open(conf) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return db, nil 24 | } 25 | 26 | func Close(db DB) error { 27 | return db.Close() 28 | } 29 | -------------------------------------------------------------------------------- /store/tikv/iterator.go: -------------------------------------------------------------------------------- 1 | // 2 | // iterator.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tikv 9 | 10 | import ( 11 | "bytes" 12 | 13 | "github.com/pingcap/tidb/kv" 14 | ) 15 | 16 | type Iterator struct { 17 | it kv.Iterator 18 | reverse bool 19 | start []byte 20 | stop []byte 21 | } 22 | 23 | // reverse not support by tikv yet 24 | func NewIterator(start []byte, stop []byte, r kv.Retriever, reverse bool) (*Iterator, error) { 25 | var ( 26 | it kv.Iterator 27 | err error 28 | ) 29 | if !reverse { 30 | it, err = r.Iter(start, nil) 31 | } else { 32 | it, err = r.IterReverse(stop) 33 | } 34 | if err != nil { 35 | return nil, err 36 | } 37 | return &Iterator{ 38 | it: it, 39 | reverse: reverse, 40 | start: start, 41 | stop: stop, 42 | }, nil 43 | } 44 | 45 | func (it *Iterator) Valid() bool { 46 | if !it.it.Valid() { 47 | return false 48 | } 49 | if !it.reverse { 50 | if bytes.Compare(it.Key(), it.stop) > 0 { 51 | return false 52 | } 53 | } else { 54 | if bytes.Compare(it.Key(), it.start) < 0 { 55 | return false 56 | } 57 | } 58 | return true 59 | } 60 | 61 | func (it *Iterator) Key() []byte { 62 | return it.it.Key() 63 | } 64 | 65 | func (it *Iterator) Value() []byte { 66 | return it.it.Value() 67 | } 68 | 69 | func (it *Iterator) Next() error { 70 | return it.it.Next() 71 | } 72 | 73 | func (it *Iterator) Close() { 74 | it.it.Close() 75 | } 76 | -------------------------------------------------------------------------------- /store/tikv/iterator_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // tikv_test.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tikv 9 | 10 | import ( 11 | "fmt" 12 | "testing" 13 | 14 | "github.com/pingcap/tidb/kv" 15 | "github.com/yongman/tidis/config" 16 | ) 17 | 18 | func TestNext(t *testing.T) { 19 | conf := &config.Config{PdAddr: "10.240.200.200:2379"} 20 | tikv, err := Open(conf) 21 | if err != nil { 22 | fmt.Println(err) 23 | } 24 | 25 | ss, err := tikv.GetNewestSnapshot() 26 | if err != nil { 27 | fmt.Println(err) 28 | } 29 | 30 | startKey := []byte{0} 31 | endKey := []byte{255, 255, 255, 255, 255, 255, 255} 32 | 33 | // reverse not support yet by tikv 34 | iter, err := NewIterator(startKey, endKey, ss.(kv.Snapshot), false) 35 | if err != nil { 36 | fmt.Println(err) 37 | } 38 | 39 | var keys int 40 | for iter.Valid() && keys < 100 { 41 | keys++ 42 | fmt.Println("key:", iter.Key()) 43 | iter.Next() 44 | } 45 | iter.Close() 46 | } 47 | -------------------------------------------------------------------------------- /store/tikv/tikv_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // tikv_test.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tikv 9 | 10 | import ( 11 | "fmt" 12 | "testing" 13 | 14 | "github.com/yongman/tidis/config" 15 | ) 16 | 17 | func TestSet(t *testing.T) { 18 | conf := &config.Config{PdAddr: "10.240.200.200:2379"} 19 | tikv, err := Open(conf) 20 | if err != nil { 21 | fmt.Println(err) 22 | } 23 | err = tikv.Set([]byte("foo"), []byte("bar")) 24 | if err != nil { 25 | fmt.Println(err) 26 | } 27 | } 28 | 29 | func TestGet(t *testing.T) { 30 | conf := &config.Config{PdAddr: "10.240.200.200:2379"} 31 | tikv, err := Open(conf) 32 | if err != nil { 33 | fmt.Println(err) 34 | } 35 | value, err := tikv.Get([]byte("foo")) 36 | fmt.Println(string(value), err) 37 | } 38 | 39 | func TestDelete(t *testing.T) { 40 | conf := &config.Config{PdAddr: "10.240.200.200:2379"} 41 | tikv, err := Open(conf) 42 | if err != nil { 43 | fmt.Println(err) 44 | } 45 | keys := make([][]byte, 1) 46 | keys[0] = []byte("foo") 47 | _, err = tikv.Delete(keys) 48 | if err != nil { 49 | fmt.Println(err) 50 | } 51 | 52 | fmt.Println("after delete") 53 | value, err := tikv.Get([]byte("foo")) 54 | fmt.Println(string(value), err) 55 | } 56 | -------------------------------------------------------------------------------- /terror/terror.go: -------------------------------------------------------------------------------- 1 | // 2 | // errors.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package terror 9 | 10 | import "errors" 11 | 12 | var ( 13 | ErrCommand error = errors.New("ERR command error") 14 | ErrCmdParams error = errors.New("ERR command params error") 15 | ErrKeyEmpty error = errors.New("ERR key cannot be empty") 16 | ErrKeyOrFieldEmpty error = errors.New("ERR key or field cannot be empty") 17 | ErrTypeNotMatch error = errors.New("ERR raw key type not match") 18 | ErrCmdInBatch error = errors.New("ERR some command in batch not supported") 19 | ErrCmdNumber error = errors.New("ERR command not enough in batch") 20 | ErrBackendType error = errors.New("ERR backend type error") 21 | ErrTypeAssertion error = errors.New("ERR interface type assertion failed") 22 | ErrOutOfIndex error = errors.New("ERR index out of range") 23 | ErrInvalidMeta error = errors.New("ERR invalid key meta") 24 | ErrUnknownType error = errors.New("ERR unknown response data type") 25 | ErrRunWithTxn error = errors.New("ERR run run with txn") 26 | ErrAuthNoNeed error = errors.New("ERR Client sent AUTH, but no password is set") 27 | ErrAuthFailed error = errors.New("ERR invalid password") 28 | ErrAuthReqired error = errors.New("NOAUTH Authentication required.") 29 | ErrKeyBusy error = errors.New("BUSYKEY key is deleting, retry later") 30 | ErrNotInteger error = errors.New("ERR value is not an integer or out of range") 31 | ErrDiscardWithoutMulti error = errors.New("ERR DISCARD without MULTI") 32 | ErrExecWithoutMulti error = errors.New("ERR EXEC without MULTI") 33 | ) 34 | -------------------------------------------------------------------------------- /tests/rediswrap.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2018 yongman 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | redis client wrapper 11 | """ 12 | 13 | import redis 14 | 15 | class RedisWrapper: 16 | def __init__(self, ip , port): 17 | self.r = redis.StrictRedis(host=ip, port=port) 18 | 19 | def get_instance(self): 20 | return self.r 21 | -------------------------------------------------------------------------------- /tests/test_hash.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2018 yongman 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | unit test for hash type 11 | """ 12 | 13 | import unittest 14 | import time 15 | from rediswrap import RedisWrapper 16 | 17 | class HashTest(unittest.TestCase): 18 | @classmethod 19 | def setUpClass(cls): 20 | print 'connect to 127.0.0.1:5379\n' 21 | cls.r = RedisWrapper('127.0.0.1', 5379).get_instance() 22 | cls.k1 = '__hash1__' 23 | cls.k2 = '__hash2__' 24 | 25 | cls.f1 = 'f1' 26 | cls.f2 = 'f2' 27 | cls.f3 = 'f3' 28 | cls.f4 = 'f4' 29 | 30 | cls.v1 = 'value1' 31 | cls.v2 = 'value2' 32 | cls.v3 = 'value3' 33 | cls.v4 = 'value4' 34 | 35 | def setUp(self): 36 | self.r.execute_command('del', self.k1) 37 | self.r.execute_command('del', self.k2) 38 | pass 39 | 40 | def test_hget(self): 41 | self.assertEqual(self.r.hset(self.k1, self.f1, self.v1), 1) 42 | self.assertEqual(self.v1, self.r.hget(self.k1, self.f1)) 43 | 44 | def test_hset(self): 45 | self.assertEqual(self.r.hset(self.k1, self.f1, self.v1), 1) 46 | self.assertEqual(self.v1, self.r.hget(self.k1, self.f1)) 47 | 48 | def test_hexists(self): 49 | self.assertEqual(self.r.hset(self.k1, self.f1, self.v1), 1) 50 | self.assertTrue(self.r.hexists(self.k1, self.f1)) 51 | 52 | def test_hstrlen(self): 53 | self.assertEqual(self.r.hset(self.k1, self.f1, self.v1), 1) 54 | self.assertEqual(self.r.hstrlen(self.k1, self.f1), len(self.v1)) 55 | 56 | def test_hlen(self): 57 | prefix = '__' 58 | for i in range(0, 200): 59 | f = '{}{}'.format(prefix, i) 60 | self.assertEqual(self.r.hset(self.k2, f, f), 1) 61 | self.assertEqual(self.r.hlen(self.k2), 200) 62 | 63 | def test_hmget(self): 64 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 65 | self.assertListEqual(self.r.hmget(self.k1, self.f1, self.f2, self.f3), [self.v1, self.v2, self.v3]) 66 | 67 | def test_hdel(self): 68 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 69 | self.assertEqual(self.r.hdel(self.k1, self.f1, self.f2, self.f3, self.f4), 3) 70 | self.assertEqual(self.r.hlen(self.k1), 0) 71 | 72 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 73 | self.assertEqual(self.r.hdel(self.k1, self.f1, self.f2), 2) 74 | self.assertEqual(self.r.hlen(self.k1), 1) 75 | 76 | def test_hkeys(self): 77 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 78 | self.assertListEqual(self.r.hkeys(self.k1), [self.f1, self.f2, self.f3]) 79 | 80 | def test_hvals(self): 81 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 82 | self.assertListEqual(self.r.hvals(self.k1), [self.v1, self.v2, self.v3]) 83 | 84 | def test_hgetall(self): 85 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 86 | self.assertDictEqual(self.r.hgetall(self.k1), {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3}) 87 | 88 | def test_del(self): 89 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 90 | self.assertTrue(self.r.execute_command("DEL", self.k1)) 91 | self.assertEqual(self.r.hlen(self.k1), 0) 92 | 93 | def test_pexpire(self): 94 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 95 | # expire in 5s 96 | self.assertEqual(self.r.execute_command("PEXPIRE", self.k1, 5000), 1) 97 | self.assertLessEqual(self.r.execute_command("PTTL", self.k1), 5000) 98 | self.assertEqual(self.r.hlen(self.k1), 3) 99 | time.sleep(6) 100 | self.assertEqual(self.r.hlen(self.k1), 0) 101 | 102 | def test_pexpireat(self): 103 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 104 | # expire in 5s 105 | ts = int(round(time.time()*1000)) + 5000 106 | self.assertEqual(self.r.execute_command('pexpireat', self.k1, ts), 1) 107 | self.assertLessEqual(self.r.execute_command('pttl', self.k1), 5000) 108 | self.assertEqual(self.r.hlen(self.k1), 3) 109 | time.sleep(6) 110 | self.assertEqual(self.r.hlen(self.k1), 0) 111 | 112 | def test_expire(self): 113 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 114 | # expire in 5s 115 | self.assertEqual(self.r.execute_command('expire', self.k1, 5), 1) 116 | self.assertLessEqual(self.r.execute_command('ttl', self.k1), 5) 117 | self.assertEqual(self.r.hlen(self.k1), 3) 118 | time.sleep(6) 119 | self.assertEqual(self.r.hlen(self.k1), 0) 120 | 121 | def test_expireat(self): 122 | self.assertTrue(self.r.hmset(self.k1, {self.f1:self.v1, self.f2:self.v2, self.f3:self.v3})) 123 | # expire in 5s 124 | ts = int(round(time.time())) + 5 125 | self.assertEqual(self.r.execute_command('expireat', self.k1, ts), 1) 126 | self.assertLessEqual(self.r.execute_command('ttl', self.k1), 5) 127 | self.assertEqual(self.r.hlen(self.k1), 3) 128 | time.sleep(6) 129 | self.assertEqual(self.r.hlen(self.k1), 0) 130 | 131 | def tearDown(self): 132 | pass 133 | 134 | @classmethod 135 | def tearDownClass(cls): 136 | cls.r.execute_command('del', cls.k1) 137 | cls.r.execute_command('del', cls.k2) 138 | print '\nclean up\n' 139 | -------------------------------------------------------------------------------- /tests/test_helper.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2018 yongman 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | all unit tests 11 | """ 12 | 13 | import unittest 14 | 15 | from test_string import StringTest 16 | from test_hash import HashTest 17 | from test_list import ListTest 18 | from test_set import SetTest 19 | from test_zset import ZsetTest 20 | from test_txn import TxnTest 21 | 22 | if __name__ == '__main__': 23 | suite = unittest.TestSuite() 24 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(StringTest)) 25 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(HashTest)) 26 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(ListTest)) 27 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(SetTest)) 28 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(ZsetTest)) 29 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TxnTest)) 30 | 31 | runner = unittest.TextTestRunner(verbosity=2) 32 | runner.run(suite) 33 | -------------------------------------------------------------------------------- /tests/test_list.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2018 yongman 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | unit test for list type 11 | """ 12 | 13 | import unittest 14 | import time 15 | import string 16 | import random 17 | from rediswrap import RedisWrapper 18 | 19 | class ListTest(unittest.TestCase): 20 | @classmethod 21 | def setUpClass(cls): 22 | print 'connect to 127.0.0.1:5379\n' 23 | cls.r = RedisWrapper('127.0.0.1', 5379).get_instance() 24 | cls.k1 = '__list1__' 25 | cls.k2 = '__list2__' 26 | cls.v1 = 'value1' 27 | cls.v2 = 'value2' 28 | 29 | def setUp(self): 30 | self.r.execute_command('del', self.k1) 31 | self.r.execute_command('del', self.k2) 32 | pass 33 | 34 | def random_string(n): 35 | return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(n)) 36 | 37 | def test_lpop(self): 38 | for i in range(200): 39 | self.assertTrue(self.r.rpush(self.k1, str(i))) 40 | for i in range(200): 41 | self.assertEqual(self.r.lpop(self.k1), str(i)) 42 | 43 | def test_lpush(self): 44 | for i in range(200): 45 | self.assertTrue(self.r.lpush(self.k1, str(i))) 46 | for i in range(200): 47 | self.assertEqual(self.r.rpop(self.k1), str(i)) 48 | 49 | def test_rpop(self): 50 | for i in range(200): 51 | self.assertTrue(self.r.lpush(self.k1, str(i))) 52 | for i in range(200): 53 | self.assertEqual(self.r.rpop(self.k1), str(i)) 54 | 55 | def test_rpush(self): 56 | for i in range(200): 57 | self.assertTrue(self.r.rpush(self.k1, str(i))) 58 | for i in range(200): 59 | self.assertEqual(self.r.lpop(self.k1), str(i)) 60 | 61 | def test_llen(self): 62 | for i in range(200): 63 | self.assertTrue(self.r.rpush(self.k1, str(i))) 64 | self.assertEqual(self.r.llen(self.k1), 200) 65 | 66 | def test_lindex(self): 67 | for i in range(200): 68 | self.assertTrue(self.r.rpush(self.k1, str(i))) 69 | for i in range(200): 70 | self.assertEqual(self.r.lindex(self.k1, i), str(i)) 71 | 72 | def test_lrange(self): 73 | for i in range(200): 74 | self.assertTrue(self.r.rpush(self.k1, str(i))) 75 | self.assertListEqual(self.r.lrange(self.k1, 10, 100), [str(i) for i in range(10, 101)]) 76 | 77 | def test_lset(self): 78 | for i in range(200): 79 | self.assertTrue(self.r.rpush(self.k1, str(i))) 80 | self.assertTrue(self.r.lset(self.k1, 100, 'hello')) 81 | self.assertEqual(self.r.lindex(self.k1, 100), 'hello') 82 | 83 | def test_ltrim(self): 84 | for i in range(200): 85 | self.assertTrue(self.r.rpush(self.k1, str(i))) 86 | self.assertTrue(self.r.ltrim(self.k1, 0, 100)) 87 | self.assertListEqual(self.r.lrange(self.k1, 0, -1), [str(i) for i in range(0, 101)]) 88 | self.assertEqual(self.r.llen(self.k1), 101) 89 | 90 | def test_del(self): 91 | for i in range(200): 92 | self.assertTrue(self.r.rpush(self.k1, str(i))) 93 | self.assertEqual(self.r.execute_command('del', self.k1), 1) 94 | 95 | def test_pexpire(self): 96 | self.assertTrue(self.r.lpush(self.k1, self.v1)) 97 | # expire in 5s 98 | self.assertTrue(self.r.execute_command('pexpire', self.k1, 5000)) 99 | self.assertLessEqual(self.r.execute_command('pttl', self.k1), 5000) 100 | self.assertEqual(self.r.llen(self.k1), 1) 101 | time.sleep(6) 102 | self.assertEqual(self.r.llen(self.k1), 0) 103 | 104 | def test_pexpireat(self): 105 | self.assertTrue(self.r.lpush(self.k1, self.v1)) 106 | # expire in 5s 107 | ts = int(round(time.time()*1000)) + 5000 108 | self.assertTrue(self.r.execute_command('pexpireat', self.k1, ts)) 109 | self.assertLessEqual(self.r.execute_command('pttl', self.k1), 5000) 110 | self.assertEqual(self.r.llen(self.k1), 1) 111 | time.sleep(6) 112 | self.assertEqual(self.r.llen(self.k1), 0) 113 | 114 | def test_expire(self): 115 | self.assertTrue(self.r.lpush(self.k1, self.v1)) 116 | # expire in 5s 117 | self.assertTrue(self.r.execute_command('expire', self.k1, 5)) 118 | self.assertLessEqual(self.r.execute_command('ttl', self.k1), 5) 119 | self.assertEqual(self.r.llen(self.k1), 1) 120 | time.sleep(6) 121 | self.assertEqual(self.r.llen(self.k1), 0) 122 | 123 | def test_expireat(self): 124 | self.assertTrue(self.r.lpush(self.k1, self.v1)) 125 | # expire in 5s 126 | ts = int(round(time.time())) + 5 127 | self.assertTrue(self.r.execute_command('expireat', self.k1, ts)) 128 | self.assertLessEqual(self.r.execute_command('ttl', self.k1), 5) 129 | self.assertEqual(self.r.llen(self.k1), 1) 130 | time.sleep(6) 131 | self.assertEqual(self.r.llen(self.k1), 0) 132 | 133 | def tearDown(self): 134 | pass 135 | 136 | @classmethod 137 | def tearDownClass(cls): 138 | cls.r.execute_command('del', cls.k1) 139 | cls.r.execute_command('del', cls.k2) 140 | print '\nclean up\n' 141 | -------------------------------------------------------------------------------- /tests/test_set.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2018 yongman 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | unit test for set type 11 | """ 12 | 13 | import unittest 14 | import time 15 | import string 16 | import random 17 | from rediswrap import RedisWrapper 18 | 19 | class SetTest(unittest.TestCase): 20 | @classmethod 21 | def setUpClass(cls): 22 | print 'connect to 127.0.0.1:5379\n' 23 | cls.r = RedisWrapper('127.0.0.1', 5379).get_instance() 24 | cls.k1 = '__set1__' 25 | cls.k2 = '__set2__' 26 | cls.k3 = '__set3__' 27 | cls.v1 = 'value1' 28 | cls.v2 = 'value2' 29 | 30 | def setUp(self): 31 | self.r.execute_command('del', self.k1) 32 | self.r.execute_command('del', self.k2) 33 | self.r.execute_command('del', self.k3) 34 | pass 35 | 36 | def random_string(n): 37 | return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(n)) 38 | 39 | def test_sadd(self): 40 | for i in range(200): 41 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 42 | self.assertEqual(self.r.scard(self.k1), 200) 43 | for i in range(200): 44 | self.assertEqual(self.r.sadd(self.k1, str(i)), 0) 45 | self.assertEqual(self.r.scard(self.k1), 200) 46 | 47 | def test_scard(self): 48 | self.assertEqual(self.r.scard(self.k1), 0) 49 | for i in range(200): 50 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 51 | self.assertEqual(self.r.scard(self.k1), 200) 52 | for i in range(200): 53 | self.assertEqual(self.r.sadd(self.k1, str(i)), 0) 54 | self.assertEqual(self.r.scard(self.k1), 200) 55 | 56 | def test_sismember(self): 57 | for i in range(100): 58 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 59 | for i in range(100): 60 | self.assertEqual(self.r.sismember(self.k1, str(i)), 1) 61 | for i in range(100, 200): 62 | self.assertEqual(self.r.sismember(self.k1, str(i)), 0) 63 | 64 | def test_smembers(self): 65 | for i in range(200): 66 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 67 | self.assertSetEqual(self.r.smembers(self.k1), set([str(i) for i in range(200)])) 68 | 69 | def test_srem(self): 70 | for i in range(200): 71 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 72 | for i in range(10,100): 73 | self.assertEqual(self.r.srem(self.k1, str(i)), 1) 74 | self.assertEqual(self.r.scard(self.k1), 199+10-i) 75 | 76 | def test_sdiff(self): 77 | for i in range(0, 150): 78 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 79 | for i in range(100, 250): 80 | self.assertEqual(self.r.sadd(self.k2, str(i)), 1) 81 | self.assertSetEqual(self.r.sdiff(self.k1, self.k2), set([str(i) for i in range(0, 100)])) 82 | self.assertSetEqual(self.r.sdiff(self.k1, '_key_not_exists'), set([str(i) for i in range(0, 150)]) ) 83 | 84 | def test_sunion(self): 85 | for i in range(0, 150): 86 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 87 | for i in range(100, 250): 88 | self.assertEqual(self.r.sadd(self.k2, str(i)), 1) 89 | self.assertSetEqual(self.r.sunion(self.k1, self.k2), set([str(i) for i in range(0, 250)])) 90 | self.assertSetEqual(self.r.sunion(self.k1, '_key_not_exists'), set([str(i) for i in range(0, 150)])) 91 | 92 | def test_sinter(self): 93 | for i in range(0, 150): 94 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 95 | for i in range(100, 250): 96 | self.assertEqual(self.r.sadd(self.k2, str(i)), 1) 97 | self.assertSetEqual(self.r.sinter(self.k1, self.k2), set([str(i) for i in range(100, 150)])) 98 | self.assertSetEqual(self.r.sinter(self.k1, '_key_not_exists'), set([str(i) for i in range(0, 150)])) 99 | 100 | def test_sdiffstore(self): 101 | for i in range(0, 150): 102 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 103 | for i in range(100, 250): 104 | self.assertEqual(self.r.sadd(self.k2, str(i)), 1) 105 | self.assertEqual(self.r.sdiffstore(self.k3, self.k1, self.k2), 100) 106 | self.assertSetEqual(self.r.smembers(self.k3), set([str(i) for i in range(0, 100)])) 107 | 108 | def test_sunionstore(self): 109 | for i in range(0, 150): 110 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 111 | for i in range(100, 250): 112 | self.assertEqual(self.r.sadd(self.k2, str(i)), 1) 113 | self.assertEqual(self.r.sunionstore(self.k3, self.k1, self.k2), 250) 114 | self.assertSetEqual(self.r.smembers(self.k3), set([str(i) for i in range(0, 250)])) 115 | 116 | def test_sinterstore(self): 117 | for i in range(0, 150): 118 | self.assertEqual(self.r.sadd(self.k1, str(i)), 1) 119 | for i in range(100, 250): 120 | self.assertEqual(self.r.sadd(self.k2, str(i)), 1) 121 | self.assertEqual(self.r.sinterstore(self.k3, self.k1, self.k2), 50) 122 | self.assertSetEqual(self.r.smembers(self.k3), set([str(i) for i in range(100, 150)])) 123 | 124 | def test_pexpire(self): 125 | self.assertEqual(self.r.sadd(self.k1, self.v1), 1) 126 | # expire in 5s 127 | self.assertTrue(self.r.execute_command('pexpire', self.k1, 5000)) 128 | self.assertLessEqual(self.r.execute_command('pttl', self.k1), 5000) 129 | self.assertEqual(self.r.scard(self.k1), 1) 130 | time.sleep(6) 131 | self.assertEqual(self.r.scard(self.k1), 0) 132 | 133 | def test_pexpireat(self): 134 | self.assertEqual(self.r.sadd(self.k1, self.v1), 1) 135 | # expire in 5s 136 | ts = int(round(time.time()*1000)) + 5000 137 | self.assertTrue(self.r.execute_command('pexpireat', self.k1, ts)) 138 | self.assertLessEqual(self.r.execute_command('pttl', self.k1), 5000) 139 | self.assertEqual(self.r.scard(self.k1), 1) 140 | time.sleep(6) 141 | self.assertEqual(self.r.scard(self.k1), 0) 142 | 143 | def test_expire(self): 144 | self.assertEqual(self.r.sadd(self.k1, self.v1), 1) 145 | # expire in 5s 146 | self.assertTrue(self.r.execute_command('expire', self.k1, 5)) 147 | self.assertLessEqual(self.r.execute_command('ttl', self.k1), 5) 148 | self.assertEqual(self.r.scard(self.k1), 1) 149 | time.sleep(6) 150 | self.assertEqual(self.r.scard(self.k1), 0) 151 | 152 | def test_expireat(self): 153 | self.assertEqual(self.r.sadd(self.k1, self.v1), 1) 154 | # expire in 5s 155 | ts = int(round(time.time())) + 5 156 | self.assertTrue(self.r.execute_command('expireat', self.k1, ts)) 157 | self.assertLessEqual(self.r.execute_command('ttl', self.k1), 5) 158 | self.assertEqual(self.r.scard(self.k1), 1) 159 | time.sleep(6) 160 | self.assertEqual(self.r.scard(self.k1), 0) 161 | 162 | def tearDown(self): 163 | pass 164 | 165 | @classmethod 166 | def tearDownClass(cls): 167 | cls.r.execute_command('del', cls.k1) 168 | cls.r.execute_command('del', cls.k2) 169 | cls.r.execute_command('del', cls.k3) 170 | print '\nclean up\n' 171 | -------------------------------------------------------------------------------- /tests/test_string.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2018 yongman 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | unit test for string type 11 | """ 12 | 13 | import unittest 14 | import time 15 | from rediswrap import RedisWrapper 16 | 17 | class StringTest(unittest.TestCase): 18 | @classmethod 19 | def setUpClass(cls): 20 | print 'connect to 127.0.0.1:5379\n' 21 | cls.r = RedisWrapper('127.0.0.1', 5379).get_instance() 22 | cls.k1 = '__string1__' 23 | cls.v1 = 'value1' 24 | cls.k2 = '__string2__' 25 | cls.v2 = 'value2' 26 | cls.bitPos = 0 27 | cls.bitVal = 1 28 | 29 | 30 | def setUp(self): 31 | self.r.delete(self.k1) 32 | self.r.delete(self.k2) 33 | pass 34 | 35 | def test_get(self): 36 | self.assertTrue(self.r.set(self.k1, self.v1)) 37 | v1 = self.r.get(self.k1) 38 | self.assertEqual(self.v1, v1, '{} != {}'.format(v1, self.v1)) 39 | 40 | def test_set(self): 41 | self.assertTrue(self.r.set(self.k1, self.v1)) 42 | v1 = self.r.get(self.k1) 43 | self.assertEqual(self.v1, v1, '{} != {}'.format(v1, self.v1)) 44 | 45 | def test_set_expire(self): 46 | self.assertTrue(self.r.set(self.k2, self.v2, px=5000)) 47 | v2 = self.r.get(self.k2) 48 | self.assertEqual(self.v2, v2, '{} != {}'.format(v2, self.v2)) 49 | 50 | self.assertTrue(self.r.set(self.k2, self.v1, ex=5)) 51 | v1 = self.r.get(self.k2) 52 | self.assertEqual(self.v1, v1, '{} != {}'.format(v1, self.v1)) 53 | 54 | def test_set_exists(self): 55 | self.assertTrue(self.r.set(self.k2, self.v2, nx=True)) 56 | v2 = self.r.get(self.k2) 57 | self.assertEqual(self.v2, v2, '{} != {}'.format(v2, self.v2)) 58 | 59 | self.assertTrue(self.r.set(self.k2, self.v1, xx=True)) 60 | v1 = self.r.get(self.k2) 61 | self.assertEqual(self.v1, v1, '{} != {}'.format(self.v1, v1)) 62 | 63 | self.assertTrue(self.r.set(self.k2, self.v2, ex=5, xx=True)) 64 | v2 = self.r.get(self.k2) 65 | self.assertEqual(self.v2, v2, '{} != {}'.format(v2, self.v2)) 66 | 67 | def test_setbit(self): 68 | ret = self.r.setbit(self.k1, self.bitPos, self.bitVal) 69 | self.assertEqual(ret, 1-self.bitVal, '{} != {}'.format(ret, 1-self.bitVal)) 70 | 71 | def test_getbit(self): 72 | ret = self.r.setbit(self.k1, self.bitPos, self.bitVal) 73 | self.assertEqual(ret, 1-self.bitVal, '{} != {}'.format(ret, 1-self.bitVal)) 74 | ret = self.r.getbit(self.k1, self.bitPos) 75 | self.assertEqual(ret, self.bitVal, '{} != {}'.format(ret, self.bitVal)) 76 | 77 | def test_bitcount(self): 78 | self.r.set(self.k1, 'foobar') 79 | ret = self.r.bitcount(self.k1) 80 | self.assertEqual(ret, 26, '{} != {}'.format(ret, '2')) 81 | 82 | def test_del(self): 83 | self.assertTrue(self.r.set(self.k1, self.v1)) 84 | v1 = self.r.get(self.k1) 85 | self.assertEqual(self.v1, v1, '{} != {}'.format(v1, self.v1)) 86 | v1 = self.r.delete(self.k1) 87 | self.assertEqual(v1, 1, '{} != 1'.format(v1)) 88 | v1 = self.r.delete(self.k1) 89 | self.assertEqual(v1, 0, '{} != 0'.format(v1)) 90 | v1 = self.r.get(self.k1) 91 | self.assertIsNone(v1, '{} != None'.format(v1)) 92 | 93 | def test_mget(self): 94 | self.assertTrue(self.r.mset({self.k1:self.v1, self.k2:self.v2})) 95 | self.assertListEqual(self.r.mget(self.k1, self.k2), [self.v1, self.v2]) 96 | 97 | def test_mset(self): 98 | self.assertTrue(self.r.mset({self.k1:self.v1, self.k2:self.v2})) 99 | self.assertListEqual(self.r.mget(self.k1, self.k2), [self.v1, self.v2]) 100 | 101 | def test_incr(self): 102 | # incr a new key 103 | self.assertEqual(self.r.incr(self.k1), 1) 104 | # incr a valid number key 105 | self.assertEqual(self.r.incr(self.k1), 2) 106 | 107 | # incr a invalid number 108 | self.assertTrue(self.r.set(self.k2, self.v2)) 109 | 110 | with self.assertRaises(Exception) as cm: 111 | self.r.incr(self.k2) 112 | err = cm.exception 113 | self.assertEqual(str(err), 'value is not an integer or out of range') 114 | 115 | def test_incrby(self): 116 | self.assertTrue(self.r.set(self.k1, 12345678)) 117 | self.assertEqual(self.r.incrby(self.k1, 12345678), 24691356) 118 | 119 | def test_decr(self): 120 | # decr a new key 121 | self.assertEqual(self.r.decr(self.k1), -1) 122 | # decr a valid number key 123 | self.assertEqual(self.r.decr(self.k1), -2) 124 | 125 | # decr a invalid number 126 | self.assertTrue(self.r.set(self.k2, self.v2)) 127 | 128 | with self.assertRaises(Exception) as cm: 129 | self.r.decr(self.k2) 130 | err = cm.exception 131 | self.assertEqual(str(err), 'value is not an integer or out of range') 132 | 133 | def test_decrby(self): 134 | self.assertTrue(self.r.set(self.k1, 12345678)) 135 | self.assertEqual(self.r.decr(self.k1, 12345679), -1) 136 | 137 | def test_strlen(self): 138 | self.assertTrue(self.r.set(self.k1, self.v1)) 139 | self.assertEqual(self.r.strlen(self.k1), len(self.v1)) 140 | 141 | def test_pexpire(self): 142 | self.assertTrue(self.r.set(self.k1, self.v1)) 143 | # expire in 5s 144 | self.assertTrue(self.r.pexpire(self.k1, 5000)) 145 | self.assertLessEqual(self.r.pttl(self.k1), 5000) 146 | self.assertEqual(self.r.get(self.k1), self.v1) 147 | time.sleep(6) 148 | self.assertIsNone(self.r.get(self.k1)) 149 | 150 | def test_pexpireat(self): 151 | self.assertTrue(self.r.set(self.k1, self.v1)) 152 | # expire in 5s 153 | ts = int(round(time.time()*1000)) + 5000 154 | self.assertTrue(self.r.pexpireat(self.k1, ts)) 155 | self.assertLessEqual(self.r.pttl(self.k1), 5000) 156 | self.assertEqual(self.r.get(self.k1), self.v1) 157 | time.sleep(6) 158 | self.assertIsNone(self.r.get(self.k1)) 159 | 160 | def test_expire(self): 161 | self.assertTrue(self.r.set(self.k1, self.v1)) 162 | # expire in 5s 163 | self.assertTrue(self.r.expire(self.k1, 5)) 164 | self.assertLessEqual(self.r.ttl(self.k1), 5) 165 | self.assertEqual(self.r.get(self.k1), self.v1) 166 | time.sleep(6) 167 | self.assertIsNone(self.r.get(self.k1)) 168 | 169 | def test_expireat(self): 170 | self.assertTrue(self.r.set(self.k1, self.v1)) 171 | # expire in 5s 172 | ts = int(round(time.time())) + 5 173 | self.assertTrue(self.r.expireat(self.k1, ts)) 174 | self.assertLessEqual(self.r.ttl(self.k1), 5) 175 | self.assertEqual(self.r.get(self.k1), self.v1) 176 | time.sleep(6) 177 | self.assertIsNone(self.r.get(self.k1)) 178 | 179 | def tearDown(self): 180 | pass 181 | 182 | @classmethod 183 | def tearDownClass(cls): 184 | cls.r.delete(cls.k1) 185 | cls.r.delete(cls.k2) 186 | print '\nclean up\n' 187 | -------------------------------------------------------------------------------- /tests/test_txn.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2018 yongman 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | unit test for transaction type 11 | """ 12 | 13 | import unittest 14 | import time 15 | from rediswrap import RedisWrapper 16 | 17 | class TxnTest(unittest.TestCase): 18 | @classmethod 19 | def setUpClass(cls): 20 | print 'connect to 127.0.0.1:5379\n' 21 | cls.r = RedisWrapper('127.0.0.1', 5379).get_instance() 22 | cls.k1 = '__string1__' 23 | cls.v1 = 'value1' 24 | cls.k2 = '__string2__' 25 | cls.v2 = 'value2' 26 | cls.bitPos = 0 27 | cls.bitVal = 1 28 | 29 | def setUp(self): 30 | self.r.delete(self.k1) 31 | self.r.delete(self.k2) 32 | pass 33 | 34 | def test_multi_empty(self): 35 | self.assertEqual(self.r.execute_command('multi'), 'OK') 36 | self.assertEqual(self.r.execute_command('exec'), None) 37 | self.assertEqual(self.r.execute_command('multi'), 'OK') 38 | self.assertEqual(self.r.execute_command('discard'), 'OK') 39 | 40 | def test_exec_without_multi(self): 41 | try: 42 | self.r.execute_command('exec') 43 | except BaseException,e: 44 | self.assertEqual(e.message, 'EXEC without MULTI') 45 | 46 | def test_discard_without_multi(self): 47 | try: 48 | self.r.execute_command('discard') 49 | except BaseException,e: 50 | self.assertEqual(e.message, 'DISCARD without MULTI') 51 | 52 | def test_exec(self): 53 | self.assertEqual(self.r.execute_command('multi'), 'OK') 54 | self.assertEqual(self.r.execute_command('set', self.k1, self.v1), 'QUEUED') 55 | self.assertEqual(self.r.execute_command('get', self.k1), 'QUEUED') 56 | self.assertEqual(self.r.execute_command('exec'), ['OK', self.v1]) 57 | 58 | def test_discard(self): 59 | self.assertEqual(self.r.execute_command('multi'), 'OK') 60 | self.assertEqual(self.r.execute_command('set', self.k1, self.v1), 'QUEUED') 61 | self.assertEqual(self.r.execute_command('get', self.k1), 'QUEUED') 62 | self.assertEqual(self.r.execute_command('discard'), 'OK') 63 | 64 | def tearDown(self): 65 | pass 66 | 67 | @classmethod 68 | def tearDownClass(cls): 69 | cls.r.delete(cls.k1) 70 | cls.r.delete(cls.k2) 71 | print '\nclean up\n' 72 | -------------------------------------------------------------------------------- /tests/test_zset.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2018 yongman 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | unit test for set type 11 | """ 12 | 13 | import unittest 14 | import time 15 | import string 16 | import random 17 | from rediswrap import RedisWrapper 18 | 19 | class ZsetTest(unittest.TestCase): 20 | @classmethod 21 | def setUpClass(cls): 22 | print 'connect to 127.0.0.1:5379\n' 23 | cls.r = RedisWrapper('127.0.0.1', 5379).get_instance() 24 | cls.k1 = '__zset1__' 25 | cls.k2 = '__zset2__' 26 | cls.v1 = 'value1' 27 | cls.v2 = 'value2' 28 | 29 | def setUp(self): 30 | self.r.execute_command('del', self.k1) 31 | self.r.execute_command('del', self.k2) 32 | pass 33 | 34 | def random_string(n): 35 | return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(n)) 36 | 37 | def test_zadd(self): 38 | for i in range(200): 39 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 1, 'zadd {} {} {} failed'.format(self.k1, i, str(i))) 40 | self.assertEqual(self.r.zcard(self.k1), 200) 41 | for i in range(200): 42 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 0) 43 | self.assertEqual(self.r.zcard(self.k1), 200) 44 | # test for add multiple member score 45 | self.assertEqual(self.r.zadd(self.k1, 1, str(1), 200, str(200)), 1) 46 | self.assertEqual(self.r.zcard(self.k1), 201) 47 | 48 | def test_zcard(self): 49 | self.assertEqual(self.r.zcard(self.k1), 0) 50 | for i in range(200): 51 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 1) 52 | self.assertEqual(self.r.zcard(self.k1), 200) 53 | for i in range(200): 54 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 0) 55 | self.assertEqual(self.r.zcard(self.k1), 200) 56 | 57 | def test_zrange(self): 58 | for i in range(100): 59 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 1) 60 | self.assertListEqual(self.r.zrange(self.k1, 0, -1, False, False), [str(i) for i in range(100)]) 61 | self.assertListEqual(self.r.zrange(self.k1, 10, 20, False, False), [str(i) for i in range(10, 21)]) 62 | self.assertListEqual(self.r.zrange(self.k1, 20, 10, False, False), []) 63 | # range with scores 64 | self.assertListEqual(self.r.zrange(self.k1, 10, 20, False, True), [(str(i), i) for i in range(10,21)]) 65 | 66 | def test_zrevrange(self): 67 | for i in range(100): 68 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 1) 69 | self.assertListEqual(self.r.zrevrange(self.k1, 0, -1, False), [str(i) for i in range(99, -1, -1)]) 70 | self.assertListEqual(self.r.zrevrange(self.k1, 10, 20,False), [str(i) for i in range(89, 78, -1)]) 71 | self.assertListEqual(self.r.zrevrange(self.k1, 20, 10,False), []) 72 | # range with scores 73 | self.assertListEqual(self.r.zrevrange(self.k1, 10, 20, True), [(str(i), i) for i in range(89, 78, -1)]) 74 | 75 | def test_zrangebyscore(self): 76 | for i in range(100): 77 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 1) 78 | self.assertListEqual(self.r.zrangebyscore(self.k1, '-inf', '+inf'), [str(i) for i in range(100)]) 79 | self.assertListEqual(self.r.zrangebyscore(self.k1, 20, 30, 2, 5), ['22', '23', '24', '25', '26']) 80 | self.assertListEqual(self.r.zrangebyscore(self.k1, 30, 20), []) 81 | self.assertListEqual(self.r.zrangebyscore(self.k1, 20, 30, None, None, True), [(str(i), i) for i in range(20, 31)]) 82 | 83 | def test_zrevrangebyscore(self): 84 | for i in range(100): 85 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 1) 86 | self.assertListEqual(self.r.zrevrangebyscore(self.k1, '+inf', '-inf'), [str(i) for i in range(99, -1, -1)]) 87 | self.assertListEqual(self.r.zrevrangebyscore(self.k1, 30, 20, 2, 5), ['28', '27', '26', '25', '24']) 88 | self.assertListEqual(self.r.zrevrangebyscore(self.k1, 20, 30), []) 89 | self.assertListEqual(self.r.zrevrangebyscore(self.k1, 30, 20, None, None, True), [(str(i), i) for i in range(30, 19, -1)]) 90 | 91 | def test_zremrangebyscore(self): 92 | for i in range(100): 93 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 1) 94 | self.assertEqual(self.r.zremrangebyscore(self.k1, 21, 30), 10) 95 | 96 | def test_zrangebylex(self): 97 | self.assertEqual(self.r.zadd(self.k1, 1, 'aaa', 2, 'aab', 3, 'abc', 4, 'bcd', 5, 'fff'), 5) 98 | self.assertListEqual(self.r.zrangebylex(self.k1, '(aaa', '[ccc'), ['aab', 'abc', 'bcd']) 99 | 100 | def test_zrevrangebylex(self): 101 | self.assertEqual(self.r.zadd(self.k1, 1, 'aaa', 2, 'aab', 3, 'abc', 4, 'bcd', 5, 'fff'), 5) 102 | self.assertListEqual(self.r.zrevrangebylex(self.k1, '[ccc', '(aaa'), ['bcd', 'abc', 'aab']) 103 | 104 | def test_zremrangebylex(self): 105 | self.assertEqual(self.r.zadd(self.k1, 1, 'aaa', 2, 'aab', 3, 'abc', 4, 'bcd', 5, 'fff'), 5) 106 | self.assertEqual(self.r.zremrangebylex(self.k1, '(aaa', '[ccc'), 3) 107 | 108 | def test_zcount(self): 109 | for i in range(100): 110 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 1) 111 | self.assertEqual(self.r.zcount(self.k1, 50, 100), 50) 112 | 113 | def test_zlexcount(self): 114 | self.assertEqual(self.r.zadd(self.k1, 1, 'aaa', 2, 'aab', 3, 'abc', 4, 'bcd', 5, 'fff'), 5) 115 | self.assertEqual(self.r.zlexcount(self.k1, '(aaa', '[ccc'), 3) 116 | 117 | def test_zscore(self): 118 | for i in range(100): 119 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 1) 120 | for i in range(100): 121 | self.assertEqual(self.r.zscore(self.k1, str(i)), i) 122 | 123 | def test_zrem(self): 124 | for i in range(100): 125 | self.assertEqual(self.r.zadd(self.k1, i, str(i)), 1) 126 | for i in range(10, 100): 127 | self.assertEqual(self.r.zrem(self.k1, str(i)), 1) 128 | self.assertEqual(self.r.zcard(self.k1), 10) 129 | 130 | def test_zincrby(self): 131 | self.assertEqual(self.r.zadd(self.k1, 10, 'member1'), 1) 132 | self.assertEqual(self.r.zincrby(self.k1, 'member1', 100), 110) 133 | self.assertEqual(self.r.zscore(self.k1, 'member1'), 110) 134 | 135 | def test_pexpire(self): 136 | self.assertEqual(self.r.zadd(self.k1, 10, self.v1), 1) 137 | # expire in 5s 138 | self.assertTrue(self.r.execute_command('pexpire', self.k1, 5000)) 139 | self.assertLessEqual(self.r.execute_command('pttl', self.k1), 5000) 140 | self.assertEqual(self.r.zcard(self.k1), 1) 141 | time.sleep(6) 142 | self.assertEqual(self.r.zcard(self.k1), 0) 143 | 144 | def test_pexpireat(self): 145 | self.assertEqual(self.r.zadd(self.k1, 10, self.v1), 1) 146 | # expire in 5s 147 | ts = int(round(time.time()*1000)) + 5000 148 | self.assertTrue(self.r.execute_command('pexpireat', self.k1, ts)) 149 | self.assertLessEqual(self.r.execute_command('pttl', self.k1), 5000) 150 | self.assertEqual(self.r.zcard(self.k1), 1) 151 | time.sleep(6) 152 | self.assertEqual(self.r.zcard(self.k1), 0) 153 | 154 | def test_expire(self): 155 | self.assertEqual(self.r.zadd(self.k1, 10, self.v1), 1) 156 | # expire in 5s 157 | self.assertTrue(self.r.execute_command('expire', self.k1, 5)) 158 | self.assertLessEqual(self.r.execute_command('ttl', self.k1), 5) 159 | self.assertEqual(self.r.zcard(self.k1), 1) 160 | time.sleep(6) 161 | self.assertEqual(self.r.zcard(self.k1), 0) 162 | 163 | def test_expireat(self): 164 | self.assertEqual(self.r.zadd(self.k1, 10, self.v1), 1) 165 | # expire in 5s 166 | ts = int(round(time.time())) + 5 167 | self.assertTrue(self.r.execute_command('expireat', self.k1, ts)) 168 | self.assertLessEqual(self.r.execute_command('ttl', self.k1), 5) 169 | self.assertEqual(self.r.zcard(self.k1), 1) 170 | time.sleep(6) 171 | self.assertEqual(self.r.zcard(self.k1), 0) 172 | 173 | def tearDown(self): 174 | pass 175 | 176 | @classmethod 177 | def tearDownClass(cls): 178 | cls.r.execute_command('del', cls.k1) 179 | cls.r.execute_command('del', cls.k2) 180 | print '\nclean up\n' 181 | -------------------------------------------------------------------------------- /tidis/async.go: -------------------------------------------------------------------------------- 1 | // 2 | // async.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | import ( 11 | "context" 12 | "github.com/yongman/go/log" 13 | ) 14 | 15 | type AsyncDelItem struct { 16 | keyType byte // user key type 17 | ukey []byte // user key 18 | } 19 | 20 | func (tidis *Tidis) AsyncDelAdd(keyType byte, ukey []byte) error { 21 | tidis.Lock.Lock() 22 | defer tidis.Lock.Unlock() 23 | 24 | key := string(keyType) + string(ukey) 25 | // key already added to chan queue 26 | if tidis.asyncDelSet.Contains(key) { 27 | return nil 28 | } 29 | tidis.asyncDelCh <- AsyncDelItem{keyType: keyType, ukey: ukey} 30 | tidis.asyncDelSet.Add(key) 31 | 32 | return nil 33 | } 34 | 35 | func (tidis *Tidis) AsyncDelDone(keyType byte, ukey []byte) error { 36 | tidis.Lock.Lock() 37 | defer tidis.Lock.Unlock() 38 | 39 | key := string(keyType) + string(ukey) 40 | if tidis.asyncDelSet.Contains(key) { 41 | tidis.asyncDelSet.Remove(key) 42 | } 43 | return nil 44 | } 45 | 46 | func (tidis *Tidis) RunAsync(ctx context.Context) { 47 | // TODO 48 | log.Infof("Async tasks started for async deletion") 49 | for { 50 | select { 51 | case item := <-tidis.asyncDelCh: 52 | key := string(item.ukey) 53 | log.Debugf("Async recv key deletion %s", key) 54 | 55 | switch item.keyType { 56 | case TLISTMETA: 57 | case THASHMETA: 58 | case TSETMETA: 59 | case TZSETMETA: 60 | default: 61 | } 62 | case <-ctx.Done(): 63 | return 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tidis/codec.go: -------------------------------------------------------------------------------- 1 | // 2 | // codec.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | import ( 11 | "github.com/yongman/go/util" 12 | "github.com/yongman/tidis/terror" 13 | ) 14 | 15 | const ( 16 | // tenant length should be less than 250, 251-255 can be used by system 17 | LeaderKey = 251 18 | GCPointKey = 252 19 | ) 20 | // encoder and decoder for key of data 21 | 22 | // tenantlen(2)|tenant|dbid(1)|typedata(1)|userkeylen(4)|userkey 23 | func RawKeyPrefix(tenantid string, dbid uint8, key []byte) []byte { 24 | buf := make([]byte, 2+len(tenantid)+1+1+4+len(key)) 25 | 26 | idx := 0 27 | util.Uint16ToBytes1(buf[idx:], uint16(len(tenantid))) 28 | idx += 2 29 | 30 | copy(buf[idx:], []byte(tenantid)) 31 | idx += len(tenantid) 32 | 33 | buf[idx], buf[idx+1] = dbid, ObjectData 34 | idx += 2 35 | 36 | util.Uint32ToBytes1(buf[idx:], uint32(len(key))) 37 | idx += 4 38 | 39 | copy(buf[idx:], key) 40 | return buf 41 | } 42 | 43 | func RawTenantPrefix(tenantId string) []byte { 44 | buf := make([]byte, 2+len(tenantId)) 45 | 46 | idx := 0 47 | util.Uint16ToBytes1(buf[idx:], uint16(len(tenantId))) 48 | idx += 2 49 | 50 | copy(buf[idx:], []byte(tenantId)) 51 | 52 | return buf 53 | } 54 | 55 | func RawDBPrefix(tenantId string, dbId uint8) []byte { 56 | buf := RawTenantPrefix(tenantId) 57 | buf = append(buf, dbId) 58 | 59 | return buf 60 | } 61 | 62 | func ZScoreOffset(score int64) uint64 { 63 | return uint64(score + ScoreMax) 64 | } 65 | 66 | func ZScoreRestore(rscore uint64) int64 { 67 | return int64(rscore - uint64(ScoreMax)) 68 | } 69 | 70 | func ZScoreDecoder(rawkeyPrefixLen int, rawkey []byte) (int64, []byte, error) { 71 | pos := rawkeyPrefixLen 72 | 73 | if rawkey[pos] != ScoreTypeKey { 74 | return 0, nil, terror.ErrTypeNotMatch 75 | } 76 | pos++ 77 | 78 | score, _ := util.BytesToUint64(rawkey[pos:]) 79 | pos = pos + 8 80 | 81 | mem := rawkey[pos:] 82 | 83 | return ZScoreRestore(score), mem, nil 84 | } 85 | 86 | func RawSysLeaderKey() []byte { 87 | b, _ := util.Uint16ToBytes(LeaderKey) 88 | return b 89 | } 90 | 91 | func RawSysGCPointKey() []byte { 92 | b, _ := util.Uint16ToBytes(GCPointKey) 93 | return b 94 | } -------------------------------------------------------------------------------- /tidis/gc.go: -------------------------------------------------------------------------------- 1 | // 2 | // gc.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | import ( 11 | "context" 12 | "github.com/pingcap/tidb/store/tikv/oracle" 13 | "github.com/yongman/go/log" 14 | "github.com/yongman/go/util" 15 | "time" 16 | ) 17 | 18 | // ttl for user key checker and operator 19 | 20 | type gcChecker struct { 21 | interval int 22 | timeout int 23 | concurrency int 24 | tdb *Tidis 25 | } 26 | 27 | func NewGCChecker(interval, timeout, concurrency int, tdb *Tidis) *gcChecker { 28 | return &gcChecker{ 29 | interval: interval, 30 | timeout: timeout, 31 | concurrency: concurrency, 32 | tdb: tdb, 33 | } 34 | } 35 | 36 | func (ch *gcChecker) Run(ctx context.Context) { 37 | log.Infof("start db gc checker with interval %d seconds", ch.interval) 38 | c := time.Tick(time.Duration(ch.interval) * time.Second) 39 | for { 40 | select { 41 | case <-c: 42 | if ch.interval == 0 { 43 | continue 44 | } 45 | if !ch.tdb.conf.Tidis.DBGCEnabled { 46 | continue 47 | } 48 | if !ch.tdb.IsLeader() { 49 | continue 50 | } 51 | // add leader check 52 | lastPoint, err := ch.loadSafePoint() 53 | if err != nil { 54 | log.Errorf("load last safe point failed, error: %s", err.Error()) 55 | continue 56 | } 57 | 58 | newPoint, err := ch.getNewPoint(time.Duration(ch.timeout) * time.Second) 59 | if err != nil { 60 | log.Errorf("get db safe point for gc error: %s", err.Error()) 61 | continue 62 | } 63 | 64 | lastPointTime := time.Unix(int64(lastPoint), 0) 65 | if newPoint.Sub(lastPointTime) < time.Duration(ch.timeout)*time.Second { 66 | log.Warnf("do not need run gc this time, %d seconds past after last gc", newPoint.Sub(lastPointTime)/time.Second) 67 | continue 68 | } 69 | 70 | safePoint := oracle.ComposeTS(oracle.GetPhysical(newPoint), 0) 71 | log.Debugf("start run db gc with safePoint %d, concurrency: %d", safePoint, 3) 72 | err = ch.tdb.RunGC(safePoint, ch.concurrency) 73 | if err != nil { 74 | log.Errorf("run gc failed, error: %s", err.Error()) 75 | } 76 | err = ch.saveSafePoint(uint64(newPoint.Unix())) 77 | case <-ctx.Done(): 78 | return 79 | } 80 | } 81 | } 82 | 83 | func (ch *gcChecker) getNewPoint(ttl time.Duration) (time.Time, error) { 84 | ver, err := ch.tdb.GetCurrentVersion() 85 | if err != nil { 86 | return time.Time{}, err 87 | } 88 | physical := oracle.ExtractPhysical(ver) 89 | sec, nsec := physical/1e3, (physical%1e3)*1e6 90 | now := time.Unix(sec, nsec) 91 | safePoint := now.Add(-ttl) 92 | return safePoint, nil 93 | } 94 | 95 | func (ch *gcChecker) saveSafePoint(ts uint64) error { 96 | gcPointKey := RawSysGCPointKey() 97 | val, err := util.Uint64ToBytes(ts) 98 | if err != nil { 99 | return err 100 | } 101 | err = ch.tdb.db.Set(gcPointKey, val) 102 | return err 103 | } 104 | 105 | func (ch *gcChecker) loadSafePoint() (uint64, error) { 106 | gcPointKey := RawSysGCPointKey() 107 | val, err := ch.tdb.db.Get(gcPointKey) 108 | if err != nil { 109 | return 0, err 110 | } 111 | if val == nil { 112 | return 0, nil 113 | } 114 | ts, err := util.BytesToUint64(val) 115 | if err != nil { 116 | return 0, err 117 | } 118 | return ts, nil 119 | } 120 | -------------------------------------------------------------------------------- /tidis/leader.go: -------------------------------------------------------------------------------- 1 | // 2 | // leader.go 3 | // Copyright (C) 2021 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | import ( 11 | "context" 12 | "github.com/yongman/go/log" 13 | "time" 14 | ) 15 | 16 | // ttl for user key checker and operator 17 | 18 | type leaderChecker struct { 19 | interval int 20 | duration int 21 | tdb *Tidis 22 | } 23 | 24 | func NewLeaderChecker(interval, duration int, tdb *Tidis) *leaderChecker { 25 | return &leaderChecker{ 26 | interval: interval, 27 | duration: duration, 28 | tdb: tdb, 29 | } 30 | } 31 | 32 | func (ch *leaderChecker) Run(ctx context.Context) { 33 | log.Infof("start leader checker with interval %d seconds", ch.interval) 34 | c := time.Tick(time.Duration(ch.interval) * time.Second) 35 | for { 36 | select { 37 | case <-c: 38 | ch.tdb.CheckLeader(ch.duration) 39 | case <-ctx.Done(): 40 | return 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tidis/t_hash.go: -------------------------------------------------------------------------------- 1 | // 2 | // t_hash.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | import ( 11 | "github.com/pingcap/tidb/kv" 12 | "github.com/yongman/go/util" 13 | "github.com/yongman/tidis/terror" 14 | "github.com/yongman/tidis/utils" 15 | ) 16 | 17 | type HashObj struct { 18 | Object 19 | Size uint64 20 | } 21 | 22 | func MarshalHashObj(obj *HashObj) []byte { 23 | totalLen := 1 + 8 + 1 + 8 24 | raw := make([]byte, totalLen) 25 | 26 | idx := 0 27 | raw[idx] = obj.Type 28 | idx++ 29 | util.Uint64ToBytes1(raw[idx:], obj.ExpireAt) 30 | idx += 8 31 | raw[idx] = obj.Tomb 32 | idx++ 33 | util.Uint64ToBytes1(raw[idx:], obj.Size) 34 | 35 | return raw 36 | } 37 | 38 | func UnmarshalHashObj(raw []byte) (*HashObj, error) { 39 | if len(raw) != 18 { 40 | return nil, nil 41 | } 42 | obj := HashObj{} 43 | idx := 0 44 | obj.Type = raw[idx] 45 | if obj.Type != THASHMETA { 46 | return nil, terror.ErrTypeNotMatch 47 | } 48 | idx++ 49 | obj.ExpireAt, _ = util.BytesToUint64(raw[idx:]) 50 | idx += 8 51 | obj.Tomb = raw[idx] 52 | idx++ 53 | obj.Size, _ = util.BytesToUint64(raw[idx:]) 54 | return &obj, nil 55 | } 56 | 57 | func (tidis *Tidis) RawHashDataKey(dbId uint8, key, field []byte) []byte { 58 | keyPrefix := tidis.RawKeyPrefix(dbId, key) 59 | dataKey := append(keyPrefix, DataTypeKey) 60 | dataKey = append(dataKey, field...) 61 | return dataKey 62 | } 63 | 64 | func (tidis *Tidis) HashMetaObj(dbId uint8, txn interface{}, key []byte) (*HashObj, error) { 65 | return tidis.HashMetaObjWithExpire(dbId, txn, key, true) 66 | } 67 | func (tidis *Tidis) HashMetaObjWithExpire(dbId uint8, txn interface{}, key []byte, checkExpire bool) (*HashObj, error) { 68 | var ( 69 | v []byte 70 | err error 71 | ) 72 | 73 | metaKey := tidis.RawKeyPrefix(dbId, key) 74 | 75 | if txn == nil { 76 | v, err = tidis.db.Get(metaKey) 77 | } else { 78 | v, err = tidis.db.GetWithTxn(metaKey, txn) 79 | } 80 | if err != nil { 81 | return nil, err 82 | } 83 | if v == nil { 84 | return nil, nil 85 | } 86 | obj, err := UnmarshalHashObj(v) 87 | if err != nil { 88 | return nil, err 89 | } 90 | if checkExpire && obj.ObjectExpired(utils.Now()) { 91 | if txn == nil { 92 | tidis.Hclear(dbId, key) 93 | } else { 94 | tidis.HclearWithTxn(dbId, txn, key) 95 | } 96 | return nil, nil 97 | } 98 | return obj, nil 99 | } 100 | 101 | func (tidis *Tidis) newHashObj() *HashObj { 102 | return &HashObj{ 103 | Object: Object{ 104 | Type: THASHMETA, 105 | Tomb: 0, 106 | ExpireAt: 0, 107 | }, 108 | Size: 0, 109 | } 110 | } 111 | 112 | func (tidis *Tidis) Hget(dbId uint8, txn interface{}, key, field []byte) ([]byte, error) { 113 | if len(key) == 0 || len(field) == 0 { 114 | return nil, terror.ErrKeyEmpty 115 | } 116 | 117 | var ( 118 | v []byte 119 | err error 120 | ) 121 | 122 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 123 | if err != nil { 124 | return nil, err 125 | } 126 | if metaObj == nil { 127 | return nil, nil 128 | } 129 | 130 | if metaObj.ObjectExpired(utils.Now()) { 131 | // TODO 132 | // hash key size > 100 use range delete otherwise use generic delete 133 | 134 | return nil, nil 135 | } 136 | 137 | eDataKey := tidis.RawHashDataKey(dbId, key, field) 138 | if txn == nil { 139 | v, err = tidis.db.Get(eDataKey) 140 | } else { 141 | v, err = tidis.db.GetWithTxn(eDataKey, txn) 142 | } 143 | if err != nil { 144 | return nil, err 145 | } 146 | 147 | return v, nil 148 | } 149 | 150 | func (tidis *Tidis) Hstrlen(dbId uint8, txn interface{}, key, field []byte) (int, error) { 151 | v, err := tidis.Hget(dbId, txn, key, field) 152 | if err != nil { 153 | return 0, err 154 | } 155 | 156 | return len(v), nil 157 | } 158 | 159 | func (tidis *Tidis) Hexists(dbId uint8, txn interface{}, key, field []byte) (bool, error) { 160 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 161 | if err != nil { 162 | return false, err 163 | } 164 | if metaObj == nil { 165 | return false, nil 166 | } 167 | 168 | if metaObj.ObjectExpired(utils.Now()) { 169 | // TODO 170 | // hash key size > 100 use range delete otherwise use generic delete 171 | 172 | return false, nil 173 | } 174 | 175 | return true, nil 176 | } 177 | 178 | func (tidis *Tidis) Hlen(dbId uint8, txn interface{}, key []byte) (uint64, error) { 179 | if len(key) == 0 { 180 | return 0, terror.ErrKeyEmpty 181 | } 182 | 183 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 184 | if err != nil { 185 | return 0, err 186 | } 187 | if metaObj == nil { 188 | return 0, nil 189 | } 190 | if metaObj.ObjectExpired(utils.Now()) { 191 | // TODO 192 | return 0, nil 193 | } 194 | 195 | return metaObj.Size, nil 196 | } 197 | 198 | func (tidis *Tidis) Hmget(dbId uint8, txn interface{}, key []byte, fields ...[]byte) ([]interface{}, error) { 199 | if len(key) == 0 || len(fields) == 0 { 200 | return nil, terror.ErrKeyOrFieldEmpty 201 | } 202 | var ( 203 | retMap map[string][]byte 204 | err error 205 | ) 206 | 207 | ret := make([]interface{}, len(fields)) 208 | 209 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 210 | if err != nil { 211 | return nil, err 212 | } 213 | if metaObj == nil { 214 | return ret, nil 215 | } 216 | if metaObj.ObjectExpired(utils.Now()) { 217 | // TODO 218 | 219 | return ret, nil 220 | } 221 | 222 | batchKeys := make([][]byte, len(fields)) 223 | for i, field := range fields { 224 | batchKeys[i] = tidis.RawHashDataKey(dbId, key, field) 225 | } 226 | if txn == nil { 227 | retMap, err = tidis.db.MGet(batchKeys) 228 | } else { 229 | retMap, err = tidis.db.MGetWithTxn(batchKeys, txn) 230 | } 231 | if err != nil { 232 | return nil, err 233 | } 234 | 235 | // convert map to slice 236 | for i, ek := range batchKeys { 237 | v, ok := retMap[string(ek)] 238 | if !ok { 239 | ret[i] = nil 240 | } else { 241 | ret[i] = v 242 | } 243 | } 244 | return ret, nil 245 | } 246 | 247 | func (tidis *Tidis) Hdel(dbId uint8, key []byte, fields ...[]byte) (uint64, error) { 248 | // txn function 249 | f := func(txn interface{}) (interface{}, error) { 250 | return tidis.HdelWithTxn(dbId, txn, key, fields...) 251 | } 252 | 253 | // execute txn 254 | deleted, err := tidis.db.BatchInTxn(f) 255 | if err != nil { 256 | return 0, err 257 | } 258 | 259 | return deleted.(uint64), nil 260 | } 261 | 262 | func (tidis *Tidis) HdelWithTxn(dbId uint8, txn interface{}, key []byte, fields ...[]byte) (uint64, error) { 263 | if len(key) == 0 || len(fields) == 0 { 264 | return 0, terror.ErrKeyOrFieldEmpty 265 | } 266 | 267 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 268 | if err != nil { 269 | return 0, err 270 | } 271 | if metaObj == nil { 272 | return 0, nil 273 | } 274 | if metaObj.ObjectExpired(utils.Now()) { 275 | // TODO 276 | 277 | return 0, nil 278 | } 279 | 280 | // txn function 281 | f := func(txn1 interface{}) (interface{}, error) { 282 | txn, ok := txn1.(kv.Transaction) 283 | if !ok { 284 | return nil, terror.ErrBackendType 285 | } 286 | 287 | var delCnt uint64 288 | 289 | if metaObj.Size == 0 { 290 | return delCnt, nil 291 | } 292 | 293 | for _, field := range fields { 294 | eDataKey := tidis.RawHashDataKey(dbId, key, field) 295 | v, err := tidis.db.GetWithTxn(eDataKey, txn) 296 | if err != nil { 297 | return nil, err 298 | } 299 | if v != nil { 300 | delCnt++ 301 | err = txn.Delete(eDataKey) 302 | if err != nil { 303 | return nil, err 304 | } 305 | } 306 | } 307 | 308 | metaObj.Size = metaObj.Size - delCnt 309 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 310 | if metaObj.Size > 0 { 311 | // update meta size 312 | eMetaValue := MarshalHashObj(metaObj) 313 | err = txn.Set(eMetaKey, eMetaValue) 314 | if err != nil { 315 | return nil, err 316 | } 317 | } else { 318 | // delete entire user hash key 319 | err = txn.Delete(eMetaKey) 320 | if err != nil { 321 | return nil, err 322 | } 323 | } 324 | 325 | return delCnt, nil 326 | } 327 | 328 | // execute txn 329 | deleted, err := tidis.db.BatchWithTxn(f, txn) 330 | if err != nil { 331 | return 0, err 332 | } 333 | 334 | return deleted.(uint64), nil 335 | } 336 | 337 | func (tidis *Tidis) Hset(dbId uint8, key, field, value []byte) (uint8, error) { 338 | // txn function 339 | f := func(txn1 interface{}) (interface{}, error) { 340 | return tidis.HsetWithTxn(dbId, txn1, key, field, value) 341 | } 342 | 343 | // execute txn 344 | ret, err := tidis.db.BatchInTxn(f) 345 | if err != nil { 346 | return 0, err 347 | } 348 | return ret.(uint8), nil 349 | } 350 | 351 | func (tidis *Tidis) HsetWithTxn(dbId uint8, txn interface{}, key, field, value []byte) (uint8, error) { 352 | if len(key) == 0 || len(field) == 0 || len(value) == 0 { 353 | return 0, terror.ErrKeyOrFieldEmpty 354 | } 355 | 356 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 357 | if err != nil { 358 | return 0, err 359 | } 360 | if metaObj == nil { 361 | metaObj = tidis.newHashObj() 362 | } 363 | 364 | // txn function 365 | f := func(txn1 interface{}) (interface{}, error) { 366 | txn, ok := txn1.(kv.Transaction) 367 | if !ok { 368 | return nil, terror.ErrBackendType 369 | } 370 | 371 | var ret uint8 372 | 373 | eDataKey := tidis.RawHashDataKey(dbId, key, field) 374 | v, err := tidis.db.GetWithTxn(eDataKey, txn) 375 | if err != nil { 376 | return nil, err 377 | } 378 | 379 | if v != nil { 380 | ret = 0 381 | } else { 382 | // new insert field, add hsize 383 | ret = 1 384 | metaObj.Size++ 385 | 386 | // update meta key 387 | eMetaValue := MarshalHashObj(metaObj) 388 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 389 | err = txn.Set(eMetaKey, eMetaValue) 390 | if err != nil { 391 | return nil, err 392 | } 393 | } 394 | 395 | // set or update field 396 | err = txn.Set(eDataKey, value) 397 | if err != nil { 398 | return 0, err 399 | } 400 | 401 | return ret, nil 402 | } 403 | 404 | // execute txn 405 | ret, err := tidis.db.BatchWithTxn(f, txn) 406 | if err != nil { 407 | return 0, err 408 | } 409 | return ret.(uint8), nil 410 | } 411 | 412 | func (tidis *Tidis) Hsetnx(dbId uint8, key, field, value []byte) (uint8, error) { 413 | // txn function 414 | f := func(txn1 interface{}) (interface{}, error) { 415 | return tidis.HsetnxWithTxn(dbId, txn1, key, field, value) 416 | } 417 | 418 | // execute txn 419 | ret, err := tidis.db.BatchInTxn(f) 420 | if err != nil { 421 | return 0, err 422 | } 423 | return ret.(uint8), nil 424 | } 425 | 426 | func (tidis *Tidis) HsetnxWithTxn(dbId uint8, txn interface{}, key, field, value []byte) (uint8, error) { 427 | if len(key) == 0 || len(field) == 0 || len(value) == 0 { 428 | return 0, terror.ErrKeyOrFieldEmpty 429 | } 430 | 431 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 432 | if err != nil { 433 | return 0, err 434 | } 435 | if metaObj == nil { 436 | metaObj = tidis.newHashObj() 437 | } 438 | 439 | // txn function 440 | f := func(txn1 interface{}) (interface{}, error) { 441 | txn, ok := txn1.(kv.Transaction) 442 | if !ok { 443 | return nil, terror.ErrBackendType 444 | } 445 | 446 | eDataKey := tidis.RawHashDataKey(dbId, key, field) 447 | v, err := tidis.db.GetWithTxn(eDataKey, txn) 448 | if err != nil { 449 | return uint8(0), err 450 | } 451 | 452 | if v != nil { 453 | // field already exists, no perform update 454 | return uint8(0), nil 455 | } 456 | 457 | // new insert field, add hsize 458 | metaObj.Size++ 459 | 460 | // update meta key 461 | eMetaData := MarshalHashObj(metaObj) 462 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 463 | err = txn.Set(eMetaKey, eMetaData) 464 | if err != nil { 465 | return uint8(0), err 466 | } 467 | 468 | // set or update field 469 | err = txn.Set(eDataKey, value) 470 | if err != nil { 471 | return uint8(0), err 472 | } 473 | 474 | return uint8(1), nil 475 | } 476 | 477 | // execute txn 478 | ret, err := tidis.db.BatchWithTxn(f, txn) 479 | if err != nil { 480 | return 0, err 481 | } 482 | return ret.(uint8), nil 483 | } 484 | 485 | // Deprecated 486 | func (tidis *Tidis) Hmset(dbId uint8, key []byte, fieldsvalues ...[]byte) error { 487 | // txn function 488 | f := func(txn1 interface{}) (interface{}, error) { 489 | return nil, tidis.HmsetWithTxn(dbId, txn1, key, fieldsvalues...) 490 | } 491 | 492 | // execute txn 493 | _, err := tidis.db.BatchInTxn(f) 494 | if err != nil { 495 | return err 496 | } 497 | 498 | return nil 499 | } 500 | 501 | // Deprecated 502 | func (tidis *Tidis) HmsetWithTxn(dbId uint8, txn interface{}, key []byte, fieldsvalues ...[]byte) error { 503 | if len(key) == 0 || len(fieldsvalues)%2 != 0 { 504 | return terror.ErrCmdParams 505 | } 506 | 507 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 508 | if err != nil { 509 | return err 510 | } 511 | if metaObj == nil { 512 | metaObj = tidis.newHashObj() 513 | } 514 | 515 | // txn function 516 | f := func(txn1 interface{}) (interface{}, error) { 517 | txn, ok := txn1.(kv.Transaction) 518 | if !ok { 519 | return nil, terror.ErrBackendType 520 | } 521 | 522 | // multi get set 523 | for i := 0; i < len(fieldsvalues)-1; i = i + 2 { 524 | field, value := fieldsvalues[i], fieldsvalues[i+1] 525 | 526 | // check field already exists, update hsize 527 | eDataKey := tidis.RawHashDataKey(dbId, key, field) 528 | v, err := tidis.db.GetWithTxn(eDataKey, txn) 529 | if err != nil { 530 | return nil, err 531 | } 532 | 533 | if v == nil { 534 | // field not exists, size should incr by one 535 | metaObj.Size++ 536 | } 537 | 538 | // update field 539 | err = txn.Set(eDataKey, value) 540 | if err != nil { 541 | return nil, err 542 | } 543 | } 544 | 545 | // update meta 546 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 547 | eMetaData := MarshalHashObj(metaObj) 548 | err = txn.Set(eMetaKey, eMetaData) 549 | if err != nil { 550 | return nil, err 551 | } 552 | 553 | return nil, nil 554 | } 555 | 556 | // execute txn 557 | _, err = tidis.db.BatchWithTxn(f, txn) 558 | if err != nil { 559 | return err 560 | } 561 | 562 | return nil 563 | } 564 | 565 | func (tidis *Tidis) Hkeys(dbId uint8, txn interface{}, key []byte) ([]interface{}, error) { 566 | if len(key) == 0 { 567 | return nil, terror.ErrKeyEmpty 568 | } 569 | 570 | var ( 571 | ss interface{} 572 | err error 573 | keys [][]byte 574 | ) 575 | 576 | if txn == nil { 577 | ss, err = tidis.db.GetNewestSnapshot() 578 | if err != nil { 579 | return nil, err 580 | } 581 | } 582 | 583 | eDataKey := tidis.RawHashDataKey(dbId, key, nil) 584 | 585 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 586 | if err != nil { 587 | return nil, err 588 | } 589 | if metaObj == nil { 590 | return nil, nil 591 | } else if metaObj.ObjectExpired(utils.Now()) { 592 | // TODO delete all hash key field 593 | 594 | return nil, nil 595 | } 596 | 597 | if txn == nil { 598 | keys, err = tidis.db.GetRangeKeys(eDataKey, nil, 0, metaObj.Size, ss) 599 | } else { 600 | keys, err = tidis.db.GetRangeKeysWithTxn(eDataKey, nil, 0, metaObj.Size, txn) 601 | } 602 | if err != nil { 603 | return nil, err 604 | } 605 | 606 | retkeys := make([]interface{}, len(keys)) 607 | keyPrefixLen := len(tidis.RawKeyPrefix(dbId, key)) 608 | for i, key := range keys { 609 | retkeys[i] = key[keyPrefixLen+1:] 610 | } 611 | 612 | return retkeys, nil 613 | } 614 | 615 | func (tidis *Tidis) Hvals(dbId uint8, txn interface{}, key []byte) ([]interface{}, error) { 616 | if len(key) == 0 { 617 | return nil, terror.ErrKeyEmpty 618 | } 619 | 620 | var ( 621 | ss interface{} 622 | err error 623 | vals [][]byte 624 | ) 625 | 626 | if txn == nil { 627 | ss, err = tidis.db.GetNewestSnapshot() 628 | if err != nil { 629 | return nil, err 630 | } 631 | } 632 | 633 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 634 | if err != nil { 635 | return nil, err 636 | } 637 | if metaObj == nil { 638 | return nil, nil 639 | } else if metaObj.ObjectExpired(utils.Now()) { 640 | // TODO delete all hash key field 641 | 642 | return nil, nil 643 | } 644 | 645 | eDataKey := tidis.RawHashDataKey(dbId, key, nil) 646 | 647 | if txn == nil { 648 | vals, err = tidis.db.GetRangeVals(eDataKey, nil, metaObj.Size, ss) 649 | } else { 650 | vals, err = tidis.db.GetRangeValsWithTxn(eDataKey, nil, metaObj.Size, txn) 651 | } 652 | if err != nil { 653 | return nil, err 654 | } 655 | 656 | retvals := make([]interface{}, len(vals)) 657 | for i, val := range vals { 658 | retvals[i] = val 659 | } 660 | 661 | return retvals, nil 662 | } 663 | 664 | func (tidis *Tidis) Hgetall(dbId uint8, txn interface{}, key []byte) ([]interface{}, error) { 665 | if len(key) == 0 { 666 | return nil, terror.ErrKeyEmpty 667 | } 668 | 669 | var ( 670 | ss interface{} 671 | kvs [][]byte 672 | err error 673 | ) 674 | 675 | if txn == nil { 676 | ss, err = tidis.db.GetNewestSnapshot() 677 | if err != nil { 678 | return nil, err 679 | } 680 | } 681 | 682 | metaObj, err := tidis.HashMetaObj(dbId, txn, key) 683 | if err != nil { 684 | return nil, err 685 | } 686 | if metaObj == nil { 687 | return nil, nil 688 | } else if metaObj.ObjectExpired(utils.Now()) { 689 | // TODO delete all hash key field 690 | 691 | return nil, nil 692 | } 693 | eDataKey := tidis.RawHashDataKey(dbId, key, nil) 694 | 695 | if txn == nil { 696 | kvs, err = tidis.db.GetRangeKeysVals(eDataKey, nil, metaObj.Size, ss) 697 | } else { 698 | kvs, err = tidis.db.GetRangeKeysValsWithTxn(eDataKey, nil, metaObj.Size, txn) 699 | } 700 | if err != nil { 701 | return nil, err 702 | } 703 | 704 | // decode fields 705 | keyPrefix := tidis.RawKeyPrefix(dbId, key) 706 | retkvs := make([]interface{}, len(kvs)) 707 | for i := 0; i < len(kvs); i = i + 1 { 708 | if i%2 == 0 { 709 | retkvs[i] = kvs[i][len(keyPrefix)+1:] // get field from data key 710 | } else { 711 | retkvs[i] = kvs[i] 712 | } 713 | } 714 | 715 | return retkvs, nil 716 | } 717 | 718 | func (tidis *Tidis) Hclear(dbId uint8, key []byte) (uint8, error) { 719 | if len(key) == 0 { 720 | return 0, terror.ErrKeyEmpty 721 | } 722 | 723 | // txn func 724 | f := func(txn interface{}) (interface{}, error) { 725 | return tidis.HclearWithTxn(dbId, txn, key) 726 | } 727 | 728 | // execute txn 729 | v, err := tidis.db.BatchInTxn(f) 730 | if err != nil { 731 | return 0, err 732 | } 733 | 734 | return v.(uint8), nil 735 | } 736 | 737 | func (tidis *Tidis) HclearWithTxn(dbId uint8, txn1 interface{}, key []byte) (uint8, error) { 738 | txn, ok := txn1.(kv.Transaction) 739 | if !ok { 740 | return uint8(0), terror.ErrBackendType 741 | } 742 | 743 | metaObj, err := tidis.HashMetaObjWithExpire(dbId, txn, key, false) 744 | if err != nil { 745 | return 0, err 746 | } 747 | if metaObj == nil { 748 | // not exist 749 | return 0, nil 750 | } 751 | 752 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 753 | // delete meta key 754 | err = txn.Delete(eMetaKey) 755 | if err != nil { 756 | return uint8(0), err 757 | } 758 | 759 | // delete all fields 760 | eDataKeyStart := tidis.RawHashDataKey(dbId, key, nil) 761 | keys, err := tidis.db.GetRangeKeysWithTxn(eDataKeyStart, nil, 0, metaObj.Size, txn) 762 | if err != nil { 763 | return uint8(0), err 764 | } 765 | for _, key := range keys { 766 | txn.Delete(key) 767 | } 768 | 769 | return uint8(1), nil 770 | } 771 | -------------------------------------------------------------------------------- /tidis/t_list.go: -------------------------------------------------------------------------------- 1 | // 2 | // t_list.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | import ( 11 | "github.com/pingcap/tidb/kv" 12 | "github.com/yongman/go/util" 13 | "github.com/yongman/tidis/terror" 14 | "github.com/yongman/tidis/utils" 15 | ) 16 | 17 | const ( 18 | LHeadDirection uint8 = 0 19 | LTailDirection uint8 = 1 20 | 21 | LItemMinIndex uint64 = 1024 22 | LItemMaxIndex uint64 = 1<<64 - 1024 23 | 24 | LItemInitIndex uint64 = 1<<32 - 512 25 | ) 26 | 27 | type ListObj struct { 28 | Object 29 | Head uint64 30 | Tail uint64 31 | Size uint64 32 | } 33 | 34 | func MarshalListObj(obj *ListObj) []byte { 35 | totalLen := 1 + 8 + 1 + 8 + 8 + 8 36 | raw := make([]byte, totalLen) 37 | 38 | idx := 0 39 | raw[idx] = obj.Type 40 | idx++ 41 | _ = util.Uint64ToBytes1(raw[idx:], obj.ExpireAt) 42 | idx += 8 43 | raw[idx] = obj.Tomb 44 | idx++ 45 | _ = util.Uint64ToBytes1(raw[idx:], obj.Head) 46 | 47 | idx += 8 48 | _ = util.Uint64ToBytes1(raw[idx:], obj.Tail) 49 | 50 | idx += 8 51 | _ = util.Uint64ToBytes1(raw[idx:], obj.Size) 52 | 53 | return raw 54 | } 55 | 56 | func UnmarshalListObj(raw []byte) (*ListObj, error) { 57 | if len(raw) != 34 { 58 | return nil, nil 59 | } 60 | obj := ListObj{} 61 | idx := 0 62 | obj.Type = raw[idx] 63 | if obj.Type != TLISTMETA { 64 | return nil, terror.ErrTypeNotMatch 65 | } 66 | idx++ 67 | obj.ExpireAt, _ = util.BytesToUint64(raw[idx:]) 68 | idx += 8 69 | obj.Tomb = raw[idx] 70 | idx++ 71 | obj.Head, _ = util.BytesToUint64(raw[idx:]) 72 | idx += 8 73 | obj.Tail, _ = util.BytesToUint64(raw[idx:]) 74 | idx += 8 75 | obj.Size, _ = util.BytesToUint64(raw[idx:]) 76 | return &obj, nil 77 | } 78 | 79 | func (tidis *Tidis) ListMetaObj(dbId uint8, txn, ss interface{}, key []byte) (*ListObj, bool, error) { 80 | return tidis.ListMetaObjWithExpire(dbId, txn, ss, key, true) 81 | } 82 | func (tidis *Tidis) ListMetaObjWithExpire(dbId uint8, txn, ss interface{}, key []byte, checkExpire bool) (*ListObj, bool, error) { 83 | var ( 84 | v []byte 85 | err error 86 | ) 87 | 88 | metaKey := tidis.RawKeyPrefix(dbId, key) 89 | 90 | if txn == nil && ss == nil { 91 | v, err = tidis.db.Get(metaKey) 92 | } else if txn == nil { 93 | v, err = tidis.db.GetWithSnapshot(metaKey, ss) 94 | } else { 95 | v, err = tidis.db.GetWithTxn(metaKey, txn) 96 | } 97 | if err != nil { 98 | return nil, false, err 99 | } 100 | if v == nil { 101 | return nil, false, nil 102 | } 103 | obj, err := UnmarshalListObj(v) 104 | if err != nil { 105 | return nil, false, err 106 | } 107 | if checkExpire && obj.ObjectExpired(utils.Now()) { 108 | if txn == nil { 109 | tidis.Ldelete(dbId, key) 110 | } else { 111 | tidis.LdelWithTxn(dbId, txn, key) 112 | } 113 | 114 | return nil, true, nil 115 | } 116 | return obj, false, nil 117 | } 118 | 119 | func (tidis *Tidis) newListMetaObj() *ListObj { 120 | return &ListObj{ 121 | Object: Object{ 122 | Tomb: 0, 123 | Type: TLISTMETA, 124 | ExpireAt: 0, 125 | }, 126 | Head: LItemInitIndex, 127 | Tail: LItemInitIndex, 128 | Size: 0, 129 | } 130 | } 131 | 132 | func (tidis *Tidis) RawListKey(dbId uint8, key []byte, idx uint64) []byte { 133 | keyPrefix := tidis.RawKeyPrefix(dbId, key) 134 | listKey := append(keyPrefix, DataTypeKey) 135 | idxBytes, _ := util.Uint64ToBytes(idx) 136 | listKey = append(listKey, idxBytes...) 137 | 138 | return listKey 139 | } 140 | 141 | func (tidis *Tidis) Lpop(dbId uint8, txn interface{}, key []byte) ([]byte, error) { 142 | if txn == nil { 143 | return tidis.lPop(dbId, key, LHeadDirection) 144 | } 145 | 146 | return tidis.lPopWithTxn(dbId, txn, key, LHeadDirection) 147 | } 148 | 149 | func (tidis *Tidis) Lpush(dbId uint8, txn interface{}, key []byte, items ...[]byte) (uint64, error) { 150 | if txn == nil { 151 | return tidis.lPush(dbId, key, LHeadDirection, items...) 152 | } 153 | 154 | return tidis.lPushWithTxn(dbId, txn, key, LHeadDirection, items...) 155 | } 156 | 157 | func (tidis *Tidis) Rpop(dbId uint8, txn interface{}, key []byte) ([]byte, error) { 158 | if txn == nil { 159 | return tidis.lPop(dbId, key, LTailDirection) 160 | } 161 | 162 | return tidis.lPopWithTxn(dbId, txn, key, LTailDirection) 163 | } 164 | 165 | func (tidis *Tidis) Rpush(dbId uint8, txn interface{}, key []byte, items ...[]byte) (uint64, error) { 166 | if txn == nil { 167 | return tidis.lPush(dbId, key, LTailDirection, items...) 168 | } 169 | 170 | return tidis.lPushWithTxn(dbId, txn, key, LTailDirection, items...) 171 | } 172 | 173 | func (tidis *Tidis) Llen(dbId uint8, txn interface{}, key []byte) (uint64, error) { 174 | if len(key) == 0 { 175 | return 0, terror.ErrKeyEmpty 176 | } 177 | 178 | metaObj, _, err := tidis.ListMetaObj(dbId, txn, nil, key) 179 | if err != nil { 180 | return 0, err 181 | } 182 | if metaObj == nil { 183 | return 0, nil 184 | } 185 | 186 | return metaObj.Size, nil 187 | } 188 | 189 | func (tidis *Tidis) Lindex(dbId uint8, txn interface{}, key []byte, index int64) ([]byte, error) { 190 | if len(key) == 0 { 191 | return nil, terror.ErrKeyEmpty 192 | } 193 | 194 | metaObj, _, err := tidis.ListMetaObj(dbId, txn, nil, key) 195 | if err != nil { 196 | return nil, err 197 | } 198 | if metaObj == nil { 199 | return nil, nil 200 | } 201 | 202 | if index >= 0 { 203 | if index >= int64(metaObj.Size) { 204 | // not exist 205 | return nil, nil 206 | } 207 | } else { 208 | if -index > int64(metaObj.Size) { 209 | // not exist 210 | return nil, nil 211 | } 212 | index = index + int64(metaObj.Size) 213 | } 214 | 215 | eDataKey := tidis.RawListKey(dbId, key, uint64(index)+metaObj.Head) 216 | 217 | return tidis.db.Get(eDataKey) 218 | } 219 | 220 | // return map[string][]byte key is encoded key, not user key 221 | func (tidis *Tidis) Lrange(dbId uint8, txn interface{}, key []byte, start, stop int64) ([]interface{}, error) { 222 | if len(key) == 0 { 223 | return nil, terror.ErrKeyEmpty 224 | } 225 | if start > stop && (stop > 0 || start < 0) { 226 | // empty range result 227 | return nil, nil 228 | } 229 | 230 | var ( 231 | retMap map[string][]byte 232 | err error 233 | ss interface{} 234 | ) 235 | 236 | if txn == nil { 237 | ss, err = tidis.db.GetNewestSnapshot() 238 | if err != nil { 239 | return nil, err 240 | } 241 | } 242 | // get meta first 243 | metaObj, _, err := tidis.ListMetaObj(dbId, txn, ss, key) 244 | if err != nil { 245 | return nil, err 246 | } 247 | if metaObj == nil { 248 | return EmptyListOrSet, nil 249 | } 250 | 251 | if start < 0 { 252 | if start < -int64(metaObj.Size) { 253 | // set start be first item index 254 | start = 0 255 | } else { 256 | start = start + int64(metaObj.Size) 257 | } 258 | } else { 259 | if start >= int64(metaObj.Size) { 260 | // empty result 261 | return nil, nil 262 | } 263 | } 264 | 265 | if stop < 0 { 266 | if stop < -int64(metaObj.Size) { 267 | // set stop be first item index 268 | stop = 0 269 | } else { 270 | // item index 271 | stop = stop + int64(metaObj.Size) 272 | } 273 | } else { 274 | if stop >= int64(metaObj.Size) { 275 | // set stop be last item index 276 | stop = int64(metaObj.Size) - 1 277 | } 278 | } 279 | 280 | // here start and stop both be positive 281 | if start > stop { 282 | return nil, nil 283 | } 284 | 285 | // generate batch request keys 286 | keys := make([][]byte, stop-start+1) 287 | 288 | for i := range keys { 289 | keys[i] = tidis.RawListKey(dbId, key, metaObj.Head+uint64(start)+uint64(i)) 290 | } 291 | 292 | // batchget 293 | if txn == nil { 294 | retMap, err = tidis.db.MGetWithSnapshot(keys, ss) 295 | } else { 296 | retMap, err = tidis.db.MGetWithTxn(keys, txn) 297 | } 298 | if err != nil { 299 | return nil, err 300 | } 301 | 302 | // convert map to array by keys sort 303 | retSlice := make([]interface{}, len(keys)) 304 | for i, k := range keys { 305 | v, ok := retMap[string(k)] 306 | if !ok { 307 | retSlice[i] = []byte(nil) 308 | } else { 309 | retSlice[i] = v 310 | } 311 | } 312 | 313 | return retSlice, nil 314 | } 315 | 316 | func (tidis *Tidis) Lset(dbId uint8, key []byte, index int64, value []byte) error { 317 | // txn function 318 | f := func(txn interface{}) (interface{}, error) { 319 | return nil, tidis.LsetWithTxn(dbId, txn, key, index, value) 320 | } 321 | 322 | // execute txn func 323 | _, err := tidis.db.BatchInTxn(f) 324 | if err != nil { 325 | return err 326 | } 327 | 328 | return nil 329 | } 330 | 331 | func (tidis *Tidis) LsetWithTxn(dbId uint8, txn interface{}, key []byte, index int64, value []byte) error { 332 | if len(key) == 0 { 333 | return terror.ErrKeyEmpty 334 | } 335 | 336 | metaObj, _, err := tidis.ListMetaObj(dbId, txn, nil, key) 337 | if err != nil { 338 | return err 339 | } 340 | if metaObj == nil { 341 | metaObj = tidis.newListMetaObj() 342 | } 343 | 344 | // txn function 345 | f := func(txn1 interface{}) (interface{}, error) { 346 | txn, ok := txn1.(kv.Transaction) 347 | if !ok { 348 | return nil, terror.ErrBackendType 349 | } 350 | 351 | if index >= 0 { 352 | if index >= int64(metaObj.Size) { 353 | // not exist 354 | return nil, terror.ErrOutOfIndex 355 | } 356 | } else { 357 | if -index > int64(metaObj.Size) { 358 | // not exist 359 | return nil, terror.ErrOutOfIndex 360 | } 361 | index = index + int64(metaObj.Size) 362 | } 363 | if index >= int64(metaObj.Size) { 364 | return nil, terror.ErrOutOfIndex 365 | } 366 | 367 | eDataKey := tidis.RawListKey(dbId, key, uint64(index)+metaObj.Head) 368 | 369 | // set item data 370 | err = txn.Set(eDataKey, value) 371 | if err != nil { 372 | return nil, err 373 | } 374 | return nil, nil 375 | } 376 | 377 | // execute txn func 378 | _, err = tidis.db.BatchWithTxn(f, txn) 379 | if err != nil { 380 | return err 381 | } 382 | 383 | return nil 384 | } 385 | 386 | func (tidis *Tidis) Ltrim(dbId uint8, key []byte, start, stop int64) error { 387 | //txn function 388 | f := func(txn interface{}) (interface{}, error) { 389 | return nil, tidis.LtrimWithTxn(dbId, txn, key, start, stop) 390 | } 391 | 392 | // execute func in txn 393 | _, err := tidis.db.BatchInTxn(f) 394 | if err != nil { 395 | return err 396 | } 397 | 398 | return nil 399 | } 400 | 401 | func (tidis *Tidis) LtrimWithTxn(dbId uint8, txn interface{}, key []byte, start, stop int64) error { 402 | if len(key) == 0 { 403 | return terror.ErrKeyEmpty 404 | } 405 | 406 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 407 | 408 | metaObj, _, err := tidis.ListMetaObj(dbId, txn, nil, key) 409 | if err != nil { 410 | return err 411 | } 412 | if metaObj == nil { 413 | return nil 414 | } 415 | 416 | //txn function 417 | f := func(txn1 interface{}) (interface{}, error) { 418 | txn, ok := txn1.(kv.Transaction) 419 | if !ok { 420 | return nil, terror.ErrBackendType 421 | } 422 | 423 | var delKey bool 424 | 425 | if start < 0 { 426 | if start < -int64(metaObj.Size) { 427 | // set start be first item index 428 | start = 0 429 | } else { 430 | start = start + int64(metaObj.Size) 431 | } 432 | } else { 433 | if start >= int64(metaObj.Size) { 434 | // all keys will be delete 435 | delKey = true 436 | } 437 | } 438 | 439 | if stop < 0 { 440 | if stop < -int64(metaObj.Size) { 441 | // set stop be first item index 442 | stop = 0 443 | } else { 444 | // item index 445 | stop = stop + int64(metaObj.Size) 446 | } 447 | } else { 448 | if stop >= int64(metaObj.Size) { 449 | // set stop be last item index 450 | stop = int64(metaObj.Size) - 1 451 | } 452 | } 453 | 454 | if start > stop { 455 | delKey = true 456 | } 457 | 458 | if delKey { 459 | // delete meta key and all items 460 | err = txn.Delete(eMetaKey) 461 | if err != nil { 462 | return nil, err 463 | } 464 | 465 | for i := start; i < stop; i++ { 466 | eDataKey := tidis.RawListKey(dbId, key, metaObj.Head+uint64(i)) 467 | err = txn.Delete(eDataKey) 468 | if err != nil { 469 | return nil, err 470 | } 471 | } 472 | } else { 473 | // update meta and delete other items 474 | head := metaObj.Head 475 | size := metaObj.Size 476 | 477 | metaObj.Head = metaObj.Head + uint64(start) 478 | metaObj.Tail = metaObj.Head + uint64(stop) + 1 479 | metaObj.Size = metaObj.Tail - metaObj.Head 480 | 481 | metaValue := MarshalListObj(metaObj) 482 | 483 | // update meta 484 | err = txn.Set(eMetaKey, metaValue) 485 | if err != nil { 486 | return nil, err 487 | } 488 | 489 | var i int64 490 | // delete front items 491 | for i = 0; i < start; i++ { 492 | eDataKey := tidis.RawListKey(dbId, key, head+uint64(i)) 493 | err = txn.Delete(eDataKey) 494 | if err != nil { 495 | return nil, err 496 | } 497 | } 498 | 499 | // delete backend items 500 | for i = stop+1; i < int64(size)-1; i++ { 501 | eDataKey := tidis.RawListKey(dbId, key, head+uint64(i)) 502 | err = txn.Delete(eDataKey) 503 | if err != nil { 504 | return nil, err 505 | } 506 | } 507 | } 508 | return nil, nil 509 | } 510 | 511 | // execute func in txn 512 | _, err = tidis.db.BatchWithTxn(f, txn) 513 | if err != nil { 514 | return err 515 | } 516 | 517 | return nil 518 | } 519 | 520 | func (tidis *Tidis) LdelWithTxn(dbId uint8, txn1 interface{}, key []byte) (int, error) { 521 | txn, ok := txn1.(kv.Transaction) 522 | if !ok { 523 | return 0, terror.ErrBackendType 524 | } 525 | 526 | metaObj, _, err := tidis.ListMetaObjWithExpire(dbId, txn1, nil, key, false) 527 | if err != nil { 528 | return 0, err 529 | } 530 | if metaObj == nil { 531 | return 0, nil 532 | } 533 | 534 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 535 | 536 | // del meta key 537 | err = txn.Delete(eMetaKey) 538 | if err != nil { 539 | return 0, err 540 | } 541 | 542 | // del items 543 | for i := metaObj.Head; i < metaObj.Tail; i++ { 544 | eDataKey := tidis.RawListKey(dbId, key, i) 545 | 546 | err = txn.Delete(eDataKey) 547 | if err != nil { 548 | return 0, err 549 | } 550 | } 551 | return 1, nil 552 | } 553 | 554 | func (tidis *Tidis) Ldelete(dbId uint8, key []byte) (int, error) { 555 | if len(key) == 0 { 556 | return 0, terror.ErrKeyEmpty 557 | } 558 | 559 | // txn func 560 | f := func(txn interface{}) (interface{}, error) { 561 | return tidis.LdelWithTxn(dbId, txn, key) 562 | } 563 | 564 | // execute txn 565 | v, err := tidis.db.BatchInTxn(f) 566 | if err != nil { 567 | return 0, err 568 | } 569 | 570 | return v.(int), nil 571 | } 572 | 573 | // head <----------------> tail 574 | // 575 | func (tidis *Tidis) lPop(dbId uint8, key []byte, direc uint8) ([]byte, error) { 576 | // txn function 577 | f := func(txn interface{}) (interface{}, error) { 578 | return tidis.lPopWithTxn(dbId, txn, key, direc) 579 | } 580 | 581 | // execute txn func 582 | ret, err := tidis.db.BatchInTxn(f) 583 | if err != nil { 584 | return nil, err 585 | } 586 | 587 | if ret == nil { 588 | return nil, nil 589 | } 590 | 591 | return ret.([]byte), nil 592 | } 593 | 594 | func (tidis *Tidis) lPopWithTxn(dbId uint8, txn interface{}, key []byte, direc uint8) ([]byte, error) { 595 | if len(key) == 0 { 596 | return nil, terror.ErrKeyEmpty 597 | } 598 | 599 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 600 | 601 | metaObj, _, err := tidis.ListMetaObj(dbId, txn, nil, key) 602 | if err != nil { 603 | return nil, err 604 | } 605 | if metaObj == nil { 606 | return nil, nil 607 | } 608 | 609 | // txn function 610 | f := func(txn1 interface{}) (interface{}, error) { 611 | txn, ok := txn1.(kv.Transaction) 612 | if !ok { 613 | return nil, terror.ErrBackendType 614 | } 615 | 616 | var eDataKey []byte 617 | 618 | // update meta 619 | if direc == LHeadDirection { 620 | eDataKey = tidis.RawListKey(dbId, key, metaObj.Head) 621 | metaObj.Head++ 622 | } else { 623 | metaObj.Tail-- 624 | eDataKey = tidis.RawListKey(dbId, key, metaObj.Tail) 625 | } 626 | metaObj.Size-- 627 | 628 | if metaObj.Size == 0 { 629 | // only one item left, delete meta 630 | err = txn.Delete(eMetaKey) 631 | if err != nil { 632 | return nil, err 633 | } 634 | } else { 635 | // update meta key 636 | // update meta, put item 637 | v := MarshalListObj(metaObj) 638 | err = txn.Set(eMetaKey, v) 639 | if err != nil { 640 | return nil, err 641 | } 642 | } 643 | 644 | // get item value 645 | item, err := txn.Get(eDataKey) 646 | if err != nil { 647 | if !kv.IsErrNotFound(err) { 648 | return nil, err 649 | } 650 | return nil, nil 651 | } 652 | 653 | // delete item 654 | err = txn.Delete(eDataKey) 655 | if err != nil { 656 | return nil, err 657 | } 658 | 659 | return item, nil 660 | } 661 | 662 | // execute txn func 663 | ret, err := tidis.db.BatchWithTxn(f, txn) 664 | if err != nil { 665 | return nil, err 666 | } 667 | 668 | if ret == nil { 669 | return nil, nil 670 | } 671 | 672 | retByte, ok := ret.([]byte) 673 | if !ok { 674 | return nil, terror.ErrTypeAssertion 675 | } 676 | 677 | return retByte, nil 678 | } 679 | 680 | // head <--------------> tail 681 | // meta [head, tail) 682 | func (tidis *Tidis) lPush(dbId uint8, key []byte, direc uint8, items ...[]byte) (uint64, error) { 683 | // txn function 684 | f := func(txn interface{}) (interface{}, error) { 685 | return tidis.lPushWithTxn(dbId, txn, key, direc, items...) 686 | } 687 | 688 | // run txn 689 | ret, err := tidis.db.BatchInTxn(f) 690 | if err != nil { 691 | return 0, err 692 | } 693 | 694 | if ret == nil { 695 | return 0, nil 696 | } 697 | 698 | return ret.(uint64), nil 699 | } 700 | 701 | func (tidis *Tidis) lPushWithTxn(dbId uint8, txn interface{}, key []byte, direc uint8, items ...[]byte) (uint64, error) { 702 | if len(key) == 0 { 703 | return 0, terror.ErrKeyEmpty 704 | } 705 | 706 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 707 | 708 | metaObj, _, err := tidis.ListMetaObj(dbId, txn, nil, key) 709 | if err != nil { 710 | return 0, err 711 | } 712 | if metaObj == nil { 713 | metaObj = tidis.newListMetaObj() 714 | } 715 | 716 | // txn function 717 | f := func(txn1 interface{}) (interface{}, error) { 718 | txn, ok := txn1.(kv.Transaction) 719 | if !ok { 720 | return nil, terror.ErrBackendType 721 | } 722 | 723 | var index uint64 724 | 725 | // update key meta 726 | itemCnt := uint64(len(items)) 727 | if direc == LHeadDirection { 728 | index = metaObj.Head 729 | metaObj.Head = metaObj.Head - itemCnt 730 | } else { 731 | index = metaObj.Tail 732 | metaObj.Tail = metaObj.Tail + itemCnt 733 | } 734 | metaObj.Size = metaObj.Size + itemCnt 735 | 736 | // encode meta value to bytes 737 | v := MarshalListObj(metaObj) 738 | 739 | // update meta, put item 740 | err = txn.Set(eMetaKey, v) 741 | if err != nil { 742 | return nil, err 743 | } 744 | 745 | var eDataKey []byte 746 | 747 | for _, item := range items { 748 | // generate item key 749 | if direc == LHeadDirection { 750 | index-- 751 | eDataKey = tidis.RawListKey(dbId, key, index) 752 | } else { 753 | eDataKey = tidis.RawListKey(dbId, key, index) 754 | index++ 755 | } 756 | err = txn.Set(eDataKey, item) 757 | if err != nil { 758 | return nil, err 759 | } 760 | } 761 | return metaObj.Size, nil 762 | } 763 | 764 | // run txn 765 | ret, err := tidis.db.BatchWithTxn(f, txn) 766 | if err != nil { 767 | return 0, err 768 | } 769 | 770 | if ret == nil { 771 | return 0, nil 772 | } 773 | 774 | return ret.(uint64), nil 775 | } 776 | -------------------------------------------------------------------------------- /tidis/t_object.go: -------------------------------------------------------------------------------- 1 | // 2 | // t_object.go 3 | // Copyright (C) 2020 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | import ( 11 | "github.com/pingcap/tidb/kv" 12 | ) 13 | 14 | type IObject interface { 15 | ObjectExpired(now uint64) bool 16 | SetExpireAt(ts uint64) 17 | TTL(now uint64) uint64 18 | IsExpireSet() bool 19 | } 20 | 21 | type Object struct { 22 | Type byte 23 | Tomb byte 24 | ExpireAt uint64 25 | } 26 | 27 | func MarshalObj(obj IObject) []byte { 28 | switch v := obj.(type) { 29 | case *StringObj: 30 | return MarshalStringObj(v) 31 | case *HashObj: 32 | return MarshalHashObj(v) 33 | case *ListObj: 34 | return MarshalListObj(v) 35 | case *SetObj: 36 | return MarshalSetObj(v) 37 | case *ZSetObj: 38 | return MarshalZSetObj(v) 39 | } 40 | return nil 41 | } 42 | 43 | func (obj *Object) ObjectExpired(now uint64) bool { 44 | if obj.ExpireAt == 0 || obj.ExpireAt > now { 45 | return false 46 | } 47 | return true 48 | } 49 | 50 | func (obj *Object) IsExpireSet() bool { 51 | if obj.ExpireAt == 0 { 52 | return false 53 | } 54 | return true 55 | } 56 | 57 | 58 | func (obj *Object) SetExpireAt(ts uint64) { 59 | obj.ExpireAt = ts 60 | } 61 | 62 | func (obj *Object) TTL(now uint64) uint64 { 63 | if obj.ExpireAt > now { 64 | return obj.ExpireAt - now 65 | } else { 66 | return 0 67 | } 68 | } 69 | 70 | func (tidis *Tidis) GetObject(dbId uint8, txn interface{}, key []byte) (byte, IObject, error) { 71 | metaKey := tidis.RawKeyPrefix(dbId, key) 72 | 73 | var ( 74 | metaValue []byte 75 | err error 76 | ) 77 | 78 | if txn != nil { 79 | metaValue, err = tidis.db.GetWithTxn(metaKey, txn) 80 | } else { 81 | metaValue, err = tidis.db.Get(metaKey) 82 | } 83 | if err != nil { 84 | return 0, nil, err 85 | } 86 | if metaValue == nil { 87 | return 0, nil, nil 88 | } 89 | 90 | var obj IObject 91 | 92 | // unmarshal with type 93 | objType := metaValue[0] 94 | switch objType { 95 | case TSTRING: 96 | obj, _ = UnmarshalStringObj(metaValue) 97 | case THASHMETA: 98 | obj, _ = UnmarshalHashObj(metaValue) 99 | case TLISTMETA: 100 | obj, _ = UnmarshalListObj(metaValue) 101 | case TSETMETA: 102 | obj, _ = UnmarshalSetObj(metaValue) 103 | case TZSETMETA: 104 | obj, _ = UnmarshalZSetObj(metaValue) 105 | } 106 | 107 | return objType, obj, nil 108 | } 109 | 110 | func (tidis *Tidis) FlushDB(dbId uint8) error { 111 | dbPrefix := RawDBPrefix(tidis.TenantId(), dbId) 112 | startKey := dbPrefix 113 | endKey := kv.Key(startKey).PrefixNext() 114 | 115 | err := tidis.db.UnsafeDeleteRange(startKey, endKey) 116 | if err != nil { 117 | return err 118 | } 119 | return nil 120 | } 121 | 122 | func (tidis *Tidis) FlushAll() error { 123 | tenantPrefix := RawTenantPrefix(tidis.TenantId()) 124 | startKey := tenantPrefix 125 | endKey := kv.Key(startKey).PrefixNext() 126 | 127 | err := tidis.db.UnsafeDeleteRange(startKey, endKey) 128 | if err != nil { 129 | return err 130 | } 131 | return nil 132 | } 133 | 134 | func (tidis *Tidis) GetCurrentVersion() (uint64, error) { 135 | return tidis.db.GetCurrentVersion() 136 | } -------------------------------------------------------------------------------- /tidis/t_set.go: -------------------------------------------------------------------------------- 1 | // 2 | // t_set.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | import ( 11 | "errors" 12 | 13 | "github.com/pingcap/tidb/kv" 14 | "github.com/yongman/go/util" 15 | "github.com/yongman/tidis/terror" 16 | "github.com/yongman/tidis/utils" 17 | 18 | mapset "github.com/deckarep/golang-set" 19 | ) 20 | 21 | const ( 22 | opDiff = iota 23 | opInter 24 | opUnion 25 | ) 26 | 27 | type SetObj struct { 28 | Object 29 | Size uint64 30 | } 31 | 32 | var ErrInvalidMeta = errors.New("invalid meta data") 33 | 34 | func MarshalSetObj(obj *SetObj) []byte { 35 | totalLen := 1 + 8 + 1 + 8 36 | raw := make([]byte, totalLen) 37 | 38 | idx := 0 39 | raw[idx] = obj.Type 40 | idx++ 41 | util.Uint64ToBytes1(raw[idx:], obj.ExpireAt) 42 | idx += 8 43 | raw[idx] = obj.Tomb 44 | idx++ 45 | util.Uint64ToBytes1(raw[idx:], obj.Size) 46 | 47 | return raw 48 | } 49 | 50 | func UnmarshalSetObj(raw []byte) (*SetObj, error) { 51 | if len(raw) != 18 { 52 | return nil, ErrInvalidMeta 53 | } 54 | obj := SetObj{} 55 | idx := 0 56 | obj.Type = raw[idx] 57 | if obj.Type != TSETMETA { 58 | return nil, terror.ErrTypeNotMatch 59 | } 60 | idx++ 61 | obj.ExpireAt, _ = util.BytesToUint64(raw[idx:]) 62 | idx += 8 63 | obj.Tomb = raw[idx] 64 | idx++ 65 | obj.Size, _ = util.BytesToUint64(raw[idx:]) 66 | return &obj, nil 67 | } 68 | 69 | func (tidis *Tidis) RawSetDataKey(dbId uint8, key, member []byte) []byte { 70 | keyPrefix := tidis.RawKeyPrefix(dbId, key) 71 | dataKey := append(keyPrefix, DataTypeKey) 72 | dataKey = append(dataKey, member...) 73 | return dataKey 74 | } 75 | 76 | func (tidis *Tidis) SetMetaObj(dbId uint8, txn, ss interface{}, key []byte) (*SetObj, bool, error) { 77 | return tidis.SetMetaObjWithExpire(dbId, txn, ss, key, true) 78 | } 79 | func (tidis *Tidis) SetMetaObjWithExpire(dbId uint8, txn, ss interface{}, key []byte, checkExpire bool) (*SetObj, bool, error) { 80 | var ( 81 | v []byte 82 | err error 83 | ) 84 | 85 | metaKey := tidis.RawKeyPrefix(dbId, key) 86 | 87 | if txn == nil && ss == nil { 88 | v, err = tidis.db.Get(metaKey) 89 | } else if txn == nil { 90 | v, err = tidis.db.GetWithSnapshot(metaKey, ss) 91 | } else { 92 | v, err = tidis.db.GetWithTxn(metaKey, txn) 93 | } 94 | if err != nil { 95 | return nil, false, err 96 | } 97 | if v == nil { 98 | return nil, false, nil 99 | } 100 | obj, err := UnmarshalSetObj(v) 101 | if err != nil { 102 | return nil, false, err 103 | } 104 | if checkExpire && obj.ObjectExpired(utils.Now()) { 105 | if txn == nil { 106 | tidis.Sclear(dbId, key) 107 | } else { 108 | tidis.SclearWithTxn(dbId, txn, key) 109 | } 110 | return nil, true, nil 111 | } 112 | return obj, false, nil 113 | } 114 | 115 | func (tidis *Tidis) newSetMetaObj() *SetObj { 116 | return &SetObj{ 117 | Object: Object{ 118 | ExpireAt: 0, 119 | Type: TSETMETA, 120 | Tomb: 0, 121 | }, 122 | Size: 0, 123 | } 124 | } 125 | 126 | func (tidis *Tidis) Sadd(dbId uint8, key []byte, members ...[]byte) (uint64, error) { 127 | // txn func 128 | f := func(txn interface{}) (interface{}, error) { 129 | return tidis.SaddWithTxn(dbId, txn, key, members...) 130 | } 131 | 132 | // execute txn 133 | v, err := tidis.db.BatchInTxn(f) 134 | if err != nil { 135 | return 0, err 136 | } 137 | 138 | return v.(uint64), nil 139 | } 140 | 141 | func (tidis *Tidis) SaddWithTxn(dbId uint8, txn interface{}, key []byte, members ...[]byte) (uint64, error) { 142 | if len(key) == 0 { 143 | return 0, terror.ErrKeyEmpty 144 | } 145 | 146 | metaObj, _, err := tidis.SetMetaObj(dbId, txn, nil, key) 147 | if err != nil { 148 | return 0, err 149 | } 150 | if metaObj == nil { 151 | metaObj = tidis.newSetMetaObj() 152 | } 153 | 154 | var added uint64 155 | 156 | // txn func 157 | f := func(txn1 interface{}) (interface{}, error) { 158 | txn, ok := txn1.(kv.Transaction) 159 | if !ok { 160 | return nil, terror.ErrBackendType 161 | } 162 | 163 | for _, member := range members { 164 | eDataKey := tidis.RawSetDataKey(dbId, key, member) 165 | // check member exists 166 | v, err := tidis.db.GetWithTxn(eDataKey, txn) 167 | if err != nil { 168 | return nil, err 169 | } 170 | if v != nil { 171 | // already exists 172 | } else { 173 | added++ 174 | err = txn.Set(eDataKey, []byte{0}) 175 | if err != nil { 176 | return nil, err 177 | } 178 | } 179 | } 180 | metaObj.Size += added 181 | // update meta 182 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 183 | eMetaValue := MarshalSetObj(metaObj) 184 | err = txn.Set(eMetaKey, eMetaValue) 185 | if err != nil { 186 | return nil, err 187 | } 188 | return added, nil 189 | } 190 | 191 | // execute txn 192 | v, err := tidis.db.BatchWithTxn(f, txn) 193 | if err != nil { 194 | return 0, err 195 | } 196 | 197 | return v.(uint64), nil 198 | } 199 | 200 | func (tidis *Tidis) Scard(dbId uint8, txn interface{}, key []byte) (uint64, error) { 201 | if len(key) == 0 { 202 | return 0, terror.ErrKeyEmpty 203 | } 204 | 205 | metaObj, _, err := tidis.SetMetaObj(dbId, txn, nil, key) 206 | if err != nil { 207 | return 0, err 208 | } 209 | if metaObj == nil { 210 | return 0, nil 211 | } 212 | 213 | return metaObj.Size, nil 214 | } 215 | 216 | func (tidis *Tidis) Sismember(dbId uint8, txn interface{}, key, member []byte) (uint8, error) { 217 | if len(key) == 0 || len(member) == 0 { 218 | return 0, terror.ErrKeyEmpty 219 | } 220 | 221 | var ( 222 | ss interface{} 223 | err error 224 | ) 225 | 226 | if txn == nil { 227 | ss, err = tidis.db.GetNewestSnapshot() 228 | if err != nil { 229 | return 0, err 230 | } 231 | } 232 | 233 | metaObj, _, err := tidis.SetMetaObj(dbId, txn, ss, key) 234 | if err != nil { 235 | return 0, err 236 | } 237 | if metaObj == nil { 238 | return 0, nil 239 | } 240 | 241 | eDataKey := tidis.RawSetDataKey(dbId, key, member) 242 | 243 | var v []byte 244 | if txn == nil { 245 | v, err = tidis.db.GetWithSnapshot(eDataKey, ss) 246 | } else { 247 | v, err = tidis.db.GetWithTxn(eDataKey, txn) 248 | } 249 | if err != nil { 250 | return 0, err 251 | } 252 | if v == nil { 253 | return 0, nil 254 | } 255 | return 1, nil 256 | } 257 | 258 | func (tidis *Tidis) Smembers(dbId uint8, txn interface{}, key []byte) ([]interface{}, error) { 259 | if len(key) == 0 { 260 | return nil, terror.ErrKeyEmpty 261 | } 262 | 263 | var ( 264 | ss interface{} 265 | err error 266 | members [][]byte 267 | ) 268 | if txn == nil { 269 | ss, err = tidis.db.GetNewestSnapshot() 270 | if err != nil { 271 | return nil, err 272 | } 273 | } 274 | 275 | metaObj, _, err := tidis.SetMetaObj(dbId, txn, ss, key) 276 | if err != nil { 277 | return nil, err 278 | } 279 | if metaObj == nil { 280 | return nil, nil 281 | } 282 | 283 | // get key range from startkey 284 | startKey := tidis.RawSetDataKey(dbId, key, nil) 285 | endKey := kv.Key(startKey).PrefixNext() 286 | 287 | if txn == nil { 288 | members, err = tidis.db.GetRangeKeys(startKey, endKey, 0, metaObj.Size, ss) 289 | } else { 290 | members, err = tidis.db.GetRangeKeysWithTxn(startKey, endKey, 0, metaObj.Size, txn) 291 | } 292 | if err != nil { 293 | return nil, err 294 | } 295 | 296 | imembers := make([]interface{}, len(members)) 297 | 298 | metaKeyLen := len(tidis.RawKeyPrefix(dbId, key)) 299 | 300 | for i, member := range members { 301 | imembers[i] = member[metaKeyLen+1:] 302 | } 303 | 304 | return imembers, nil 305 | } 306 | 307 | func (tidis *Tidis) skeyExists(dbId uint8, metaKey []byte, ss, txn interface{}) (bool, error) { 308 | metaObj, _, err := tidis.SetMetaObj(dbId, txn, ss, metaKey) 309 | if err != nil { 310 | return false, err 311 | } 312 | if metaObj == nil { 313 | return false, nil 314 | } 315 | return true, nil 316 | } 317 | 318 | func (tidis *Tidis) Srem(dbId uint8, key []byte, members ...[]byte) (uint64, error) { 319 | if len(key) == 0 || len(members) == 0 { 320 | return 0, terror.ErrKeyEmpty 321 | } 322 | 323 | // txn func 324 | f := func(txn interface{}) (interface{}, error) { 325 | return tidis.SremWithTxn(dbId, txn, key, members...) 326 | } 327 | 328 | // execute txn 329 | v1, err := tidis.db.BatchInTxn(f) 330 | if err != nil { 331 | return 0, err 332 | } 333 | 334 | return v1.(uint64), nil 335 | } 336 | 337 | func (tidis *Tidis) SremWithTxn(dbId uint8, txn interface{}, key []byte, members ...[]byte) (uint64, error) { 338 | if len(key) == 0 || len(members) == 0 { 339 | return 0, terror.ErrKeyEmpty 340 | } 341 | 342 | var removed uint64 343 | 344 | metaObj, _, err := tidis.SetMetaObj(dbId, txn, nil, key) 345 | 346 | // txn func 347 | f := func(txn1 interface{}) (interface{}, error) { 348 | txn, ok := txn1.(kv.Transaction) 349 | if !ok { 350 | return nil, terror.ErrBackendType 351 | } 352 | 353 | for _, member := range members { 354 | // check exists 355 | eDataKey := tidis.RawSetDataKey(dbId, key, member) 356 | v, err := tidis.db.GetWithTxn(eDataKey, txn) 357 | if err != nil { 358 | return nil, err 359 | } 360 | if v != nil { 361 | removed++ 362 | err = txn.Delete(eDataKey) 363 | if err != nil { 364 | return nil, err 365 | } 366 | } 367 | } 368 | 369 | if removed > 0 { 370 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 371 | // update meta 372 | metaObj.Size -= removed 373 | // update meta 374 | if metaObj.Size > 0 { 375 | eMetaValue := MarshalSetObj(metaObj) 376 | err = txn.Set(eMetaKey, eMetaValue) 377 | if err != nil { 378 | return nil, err 379 | } 380 | } else { 381 | // ssize == 0, delete meta 382 | err = txn.Delete(eMetaKey) 383 | if err != nil { 384 | return nil, err 385 | } 386 | } 387 | } 388 | 389 | return removed, nil 390 | } 391 | 392 | // execute txn 393 | v1, err := tidis.db.BatchWithTxn(f, txn) 394 | if err != nil { 395 | return 0, err 396 | } 397 | 398 | return v1.(uint64), nil 399 | } 400 | 401 | func (tidis *Tidis) newSetsFromKeys(dbId uint8, ss, txn interface{}, keys ...[]byte) ([]mapset.Set, error) { 402 | mss := make([]mapset.Set, len(keys)) 403 | 404 | var ( 405 | members [][]byte 406 | ) 407 | 408 | for i, k := range keys { 409 | metaObj, _, err := tidis.SetMetaObj(dbId, txn, ss, k) 410 | if err != nil { 411 | return nil, err 412 | } 413 | 414 | if metaObj == nil { 415 | // key not exists 416 | mss[i] = nil 417 | continue 418 | } 419 | 420 | startKey := tidis.RawSetDataKey(dbId, k, nil) 421 | endKey := kv.Key(startKey).PrefixNext() 422 | 423 | if txn == nil { 424 | members, err = tidis.db.GetRangeKeys(startKey, endKey, 0, metaObj.Size, ss) 425 | } else { 426 | members, err = tidis.db.GetRangeKeysWithTxn(startKey, endKey, 0, metaObj.Size, txn) 427 | } 428 | if err != nil { 429 | return nil, err 430 | } 431 | 432 | keyPrefixLen := len(tidis.RawKeyPrefix(dbId, k)) 433 | // create new set 434 | strMembers := make([]interface{}, len(members)) 435 | for i, member := range members { 436 | s := member[keyPrefixLen+1:] 437 | strMembers[i] = string(s) 438 | } 439 | mss[i] = mapset.NewSet(strMembers...) 440 | } 441 | return mss, nil 442 | } 443 | 444 | func (tidis *Tidis) Sops(dbId uint8, txn interface{}, opType int, keys ...[]byte) ([]interface{}, error) { 445 | if len(keys) == 0 { 446 | return nil, terror.ErrKeyEmpty 447 | } 448 | 449 | var ( 450 | ss interface{} 451 | err error 452 | ) 453 | if txn == nil { 454 | ss, err = tidis.db.GetNewestSnapshot() 455 | if err != nil { 456 | return nil, err 457 | } 458 | } 459 | 460 | mss, err := tidis.newSetsFromKeys(dbId, ss, txn, keys...) 461 | if err != nil { 462 | return nil, err 463 | } 464 | 465 | var ( 466 | opSet mapset.Set = nil 467 | i int 468 | ) 469 | 470 | for j, ms1 := range mss { 471 | if j == 0 && ms1 == nil && opType == opDiff { 472 | return make([]interface{}, 0), nil 473 | } 474 | if ms1 == nil { 475 | continue 476 | } 477 | if i == 0 { 478 | opSet = ms1 479 | } else { 480 | ms := ms1.(mapset.Set) 481 | switch opType { 482 | case opDiff: 483 | opSet = opSet.Difference(ms) 484 | break 485 | case opInter: 486 | opSet = opSet.Intersect(ms) 487 | break 488 | case opUnion: 489 | opSet = opSet.Union(ms) 490 | break 491 | } 492 | } 493 | i++ 494 | } 495 | if opSet == nil { 496 | return make([]interface{}, 0), nil 497 | } 498 | 499 | return opSet.ToSlice(), nil 500 | } 501 | 502 | func (tidis *Tidis) Sdiff(dbId uint8, txn interface{}, keys ...[]byte) ([]interface{}, error) { 503 | return tidis.Sops(dbId, txn, opDiff, keys...) 504 | } 505 | 506 | func (tidis *Tidis) Sinter(dbId uint8, txn interface{}, keys ...[]byte) ([]interface{}, error) { 507 | return tidis.Sops(dbId, txn, opInter, keys...) 508 | } 509 | 510 | func (tidis *Tidis) Sunion(dbId uint8, txn interface{}, keys ...[]byte) ([]interface{}, error) { 511 | return tidis.Sops(dbId, txn, opUnion, keys...) 512 | } 513 | 514 | func (tidis *Tidis) SclearKeyWithTxn(dbId uint8, txn1 interface{}, key []byte) (int, error) { 515 | eMetaKey := tidis.RawKeyPrefix(dbId, key) 516 | 517 | txn, ok := txn1.(kv.Transaction) 518 | if !ok { 519 | return 0, terror.ErrBackendType 520 | } 521 | 522 | metaObj, _, err := tidis.SetMetaObjWithExpire(dbId, txn, nil, key, false) 523 | // check key exists 524 | if err != nil { 525 | return 0, err 526 | } 527 | if metaObj == nil { 528 | // not exists 529 | return 0, nil 530 | } 531 | 532 | // delete meta key and all members 533 | err = txn.Delete(eMetaKey) 534 | if err != nil { 535 | return 0, err 536 | } 537 | 538 | startKey := tidis.RawSetDataKey(dbId, key, nil) 539 | endKey := kv.Key(startKey).PrefixNext() 540 | 541 | _, err = tidis.db.DeleteRangeWithTxn(startKey, endKey, metaObj.Size, txn) 542 | if err != nil { 543 | return 0, err 544 | } 545 | 546 | return 1, nil 547 | } 548 | 549 | func (tidis *Tidis) Sclear(dbId uint8, keys ...[]byte) (uint64, error) { 550 | if len(keys) == 0 { 551 | return 0, terror.ErrKeyEmpty 552 | } 553 | 554 | // clear all keys in one txn 555 | // txn func 556 | f := func(txn interface{}) (interface{}, error) { 557 | return tidis.SclearWithTxn(dbId, txn, keys...) 558 | } 559 | 560 | // execute txn 561 | v, err := tidis.db.BatchInTxn(f) 562 | if err != nil { 563 | return 0, err 564 | } 565 | 566 | return v.(uint64), nil 567 | } 568 | 569 | func (tidis *Tidis) SclearWithTxn(dbId uint8, txn interface{}, keys ...[]byte) (uint64, error) { 570 | if len(keys) == 0 { 571 | return 0, terror.ErrKeyEmpty 572 | } 573 | 574 | // clear all keys in one txn 575 | // txn func 576 | f := func(txn1 interface{}) (interface{}, error) { 577 | txn, ok := txn1.(kv.Transaction) 578 | if !ok { 579 | return nil, terror.ErrBackendType 580 | } 581 | 582 | var deleted uint64 583 | 584 | // clear each key 585 | for _, key := range keys { 586 | _, err := tidis.SclearKeyWithTxn(dbId, txn, key) 587 | if err != nil { 588 | return 0, err 589 | } 590 | 591 | deleted++ 592 | } 593 | 594 | return deleted, nil 595 | } 596 | 597 | // execute txn 598 | v, err := tidis.db.BatchWithTxn(f, txn) 599 | if err != nil { 600 | return 0, err 601 | } 602 | 603 | return v.(uint64), nil 604 | } 605 | 606 | func (tidis *Tidis) SopsStore(dbId uint8, opType int, dest []byte, keys ...[]byte) (uint64, error) { 607 | // write in txn 608 | f := func(txn interface{}) (interface{}, error) { 609 | return tidis.SopsStoreWithTxn(dbId, txn, opType, dest, keys...) 610 | } 611 | 612 | // execute in txn 613 | v, err := tidis.db.BatchInTxn(f) 614 | if err != nil { 615 | return 0, err 616 | } 617 | 618 | return v.(uint64), nil 619 | } 620 | 621 | func (tidis *Tidis) SopsStoreWithTxn(dbId uint8, txn interface{}, opType int, dest []byte, keys ...[]byte) (uint64, error) { 622 | if len(dest) == 0 || len(keys) == 0 { 623 | return uint64(0), terror.ErrKeyEmpty 624 | } 625 | 626 | destMetaObj, _, err := tidis.SetMetaObj(dbId, txn, nil, dest) 627 | if err != nil { 628 | return uint64(0), err 629 | } 630 | 631 | // write in txn 632 | f := func(txn1 interface{}) (interface{}, error) { 633 | txn, ok := txn1.(kv.Transaction) 634 | if !ok { 635 | return uint64(0), terror.ErrBackendType 636 | } 637 | 638 | // get result set from keys ops 639 | mss, err := tidis.newSetsFromKeys(dbId, nil, txn, keys...) 640 | if err != nil { 641 | return uint64(0), err 642 | } 643 | 644 | var opSet mapset.Set 645 | var i int 646 | 647 | for j, ms1 := range mss { 648 | if j == 0 && ms1 == nil && opType == opDiff { 649 | return uint64(0), nil 650 | } 651 | if ms1 == nil { 652 | continue 653 | } 654 | ms := ms1.(mapset.Set) 655 | if i == 0 { 656 | opSet = ms 657 | } else { 658 | switch opType { 659 | case opDiff: 660 | opSet = opSet.Difference(ms) 661 | break 662 | case opInter: 663 | opSet = opSet.Intersect(ms) 664 | break 665 | case opUnion: 666 | opSet = opSet.Union(ms) 667 | break 668 | } 669 | } 670 | i++ 671 | } 672 | 673 | eDestMetaKey := tidis.RawKeyPrefix(dbId, dest) 674 | 675 | if destMetaObj != nil { 676 | // startkey 677 | startKey := tidis.RawSetDataKey(dbId, dest, nil) 678 | endKey := kv.Key(startKey).PrefixNext() 679 | _, err = tidis.db.DeleteRangeWithTxn(startKey, endKey, destMetaObj.Size, txn) 680 | if err != nil { 681 | return uint64(0), err 682 | } 683 | err = txn.Delete(eDestMetaKey) 684 | if err != nil { 685 | return uint64(0), err 686 | } 687 | } else { 688 | destMetaObj = tidis.newSetMetaObj() 689 | } 690 | if opSet == nil || opSet.Cardinality() == 0 { 691 | return uint64(0), nil 692 | } 693 | 694 | // save opset to new dest key 695 | for _, member := range opSet.ToSlice() { 696 | eDataKey := tidis.RawSetDataKey(dbId, dest, []byte(member.(string))) 697 | err = txn.Set(eDataKey, []byte{0}) 698 | if err != nil { 699 | return uint64(0), err 700 | } 701 | } 702 | // save dest meta key 703 | destMetaObj.Size = uint64(opSet.Cardinality()) 704 | eDestMetaValue := MarshalSetObj(destMetaObj) 705 | err = txn.Set(eDestMetaKey, eDestMetaValue) 706 | if err != nil { 707 | return uint64(0), err 708 | } 709 | 710 | return destMetaObj.Size, nil 711 | } 712 | 713 | // execute in txn 714 | v, err := tidis.db.BatchWithTxn(f, txn) 715 | if err != nil { 716 | return 0, err 717 | } 718 | 719 | return v.(uint64), nil 720 | } 721 | 722 | func (tidis *Tidis) Sdiffstore(dbId uint8, dest []byte, keys ...[]byte) (uint64, error) { 723 | return tidis.SopsStore(dbId, opDiff, dest, keys...) 724 | } 725 | 726 | func (tidis *Tidis) Sinterstore(dbId uint8, dest []byte, keys ...[]byte) (uint64, error) { 727 | return tidis.SopsStore(dbId, opInter, dest, keys...) 728 | } 729 | 730 | func (tidis *Tidis) Sunionstore(dbId uint8, dest []byte, keys ...[]byte) (uint64, error) { 731 | return tidis.SopsStore(dbId, opUnion, dest, keys...) 732 | } 733 | 734 | func (tidis *Tidis) SdiffstoreWithTxn(dbId uint8, txn interface{}, dest []byte, keys ...[]byte) (uint64, error) { 735 | return tidis.SopsStoreWithTxn(dbId, txn, opDiff, dest, keys...) 736 | } 737 | 738 | func (tidis *Tidis) SinterstoreWithTxn(dbId uint8, txn interface{}, dest []byte, keys ...[]byte) (uint64, error) { 739 | return tidis.SopsStoreWithTxn(dbId, txn, opInter, dest, keys...) 740 | } 741 | 742 | func (tidis *Tidis) SunionstoreWithTxn(dbId uint8, txn interface{}, dest []byte, keys ...[]byte) (uint64, error) { 743 | return tidis.SopsStoreWithTxn(dbId, txn, opUnion, dest, keys...) 744 | } 745 | -------------------------------------------------------------------------------- /tidis/t_string.go: -------------------------------------------------------------------------------- 1 | // 2 | // t_string.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | import ( 11 | "github.com/pingcap/tidb/kv" 12 | "github.com/yongman/go/util" 13 | "github.com/yongman/tidis/terror" 14 | "github.com/yongman/tidis/utils" 15 | "time" 16 | ) 17 | 18 | type StringObj struct { 19 | Object 20 | Value []byte 21 | } 22 | 23 | func MarshalStringObj(obj *StringObj) []byte { 24 | totalLen := 1 + 8 + 1 + len(obj.Value) 25 | raw := make([]byte, totalLen) 26 | 27 | idx := 0 28 | raw[idx] = obj.Type 29 | idx++ 30 | util.Uint64ToBytes1(raw[idx:], obj.ExpireAt) 31 | idx += 8 32 | raw[idx] = obj.Tomb 33 | idx++ 34 | copy(raw[idx:], obj.Value) 35 | 36 | return raw 37 | } 38 | 39 | func UnmarshalStringObj(raw []byte) (*StringObj, error) { 40 | if len(raw) < 10 { 41 | return nil, nil 42 | } 43 | obj := StringObj{} 44 | idx := 0 45 | obj.Type = raw[idx] 46 | if obj.Type != TSTRING { 47 | return nil, terror.ErrTypeNotMatch 48 | } 49 | idx++ 50 | obj.ExpireAt, _ = util.BytesToUint64(raw[idx:]) 51 | idx += 8 52 | obj.Tomb = raw[idx] 53 | idx++ 54 | obj.Value = raw[idx:] 55 | return &obj, nil 56 | } 57 | 58 | func (tidis *Tidis) Get(dbId uint8, txn interface{}, key []byte) ([]byte, error) { 59 | if len(key) == 0 { 60 | return nil, terror.ErrKeyEmpty 61 | } 62 | 63 | key = tidis.RawKeyPrefix(dbId, key) 64 | 65 | var ( 66 | v []byte 67 | err error 68 | ) 69 | 70 | if txn == nil { 71 | v, err = tidis.db.Get(key) 72 | } else { 73 | v, err = tidis.db.GetWithTxn(key, txn) 74 | } 75 | if err != nil { 76 | return nil, err 77 | } 78 | // key not exist, return asap 79 | if v == nil { 80 | return nil, nil 81 | } 82 | 83 | obj, err := UnmarshalStringObj(v) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | if obj.ObjectExpired(utils.Now()) { 89 | if txn == nil { 90 | tidis.db.Delete([][]byte{key}) 91 | } else { 92 | tidis.db.DeleteWithTxn([][]byte{key}, txn) 93 | } 94 | return nil, nil 95 | } 96 | 97 | return obj.Value, nil 98 | } 99 | 100 | func (tidis *Tidis) MGet(dbId uint8, txn interface{}, keys [][]byte) ([]interface{}, error) { 101 | if len(keys) == 0 { 102 | return nil, terror.ErrKeyEmpty 103 | } 104 | 105 | var ( 106 | m map[string][]byte 107 | err error 108 | ) 109 | for i := 0; i < len(keys); i++ { 110 | keys[i] = tidis.RawKeyPrefix(dbId, keys[i]) 111 | } 112 | 113 | if txn == nil { 114 | m, err = tidis.db.MGet(keys) 115 | } else { 116 | m, err = tidis.db.MGetWithTxn(keys, txn) 117 | } 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | resp := make([]interface{}, len(keys)) 123 | for i, key := range keys { 124 | if v, ok := m[string(key)]; ok { 125 | obj, err := UnmarshalStringObj(v) 126 | if err != nil { 127 | resp[i] = nil 128 | continue 129 | } else if obj.ObjectExpired(utils.Now()) { 130 | resp[i] = nil 131 | if txn == nil { 132 | tidis.db.Delete([][]byte{key}) 133 | } else { 134 | tidis.db.DeleteWithTxn([][]byte{key}, txn) 135 | } 136 | } else { 137 | resp[i] = obj.Value 138 | } 139 | } else { 140 | resp[i] = nil 141 | } 142 | } 143 | return resp, nil 144 | } 145 | 146 | func (tidis *Tidis) Set(dbId uint8, txn interface{}, key, value []byte) error { 147 | if len(key) == 0 { 148 | return terror.ErrKeyEmpty 149 | } 150 | 151 | var err error 152 | 153 | key = tidis.RawKeyPrefix(dbId, key) 154 | 155 | obj := StringObj{ 156 | Object: Object{ 157 | Type: TSTRING, 158 | Tomb: 0, 159 | ExpireAt: 0, 160 | }, 161 | Value: value, 162 | } 163 | v := MarshalStringObj(&obj) 164 | 165 | if txn == nil { 166 | err = tidis.db.Set(key, v) 167 | } else { 168 | err = tidis.db.SetWithTxn(key, v, txn) 169 | } 170 | if err != nil { 171 | return err 172 | } 173 | return nil 174 | } 175 | 176 | func (tidis *Tidis) SetWithParam(dbId uint8, txn interface{}, key, value []byte, msTtl uint64, nxFlag bool, xxFlag bool) (bool, error) { 177 | if len(key) == 0 { 178 | return false, terror.ErrKeyEmpty 179 | } 180 | 181 | if nxFlag == true && xxFlag == true { 182 | return false, terror.ErrCmdParams 183 | } 184 | 185 | obj := StringObj{ 186 | Object: Object{ 187 | Type: TSTRING, 188 | Tomb: 0, 189 | ExpireAt: 0, 190 | }, 191 | Value: value, 192 | } 193 | 194 | var err error 195 | 196 | f := func(txn interface{}) (interface{}, error) { 197 | tValue, err := tidis.Get(dbId, txn, key) 198 | if err != nil { 199 | return false, err 200 | } 201 | 202 | if nxFlag == true && tValue != nil { 203 | return false, nil 204 | } 205 | 206 | if xxFlag == true && tValue == nil { 207 | return false, nil 208 | } 209 | 210 | if msTtl > 0 { 211 | obj.ExpireAt = utils.Now() + msTtl 212 | } 213 | 214 | value = MarshalStringObj(&obj) 215 | metaKey := tidis.RawKeyPrefix(dbId, key) 216 | err = tidis.db.SetWithTxn(metaKey, value, txn) 217 | if err != nil { 218 | return false, err 219 | } 220 | 221 | return true, nil 222 | } 223 | 224 | var result interface{} 225 | if txn == nil { 226 | result, err = tidis.db.BatchInTxn(f) 227 | } else { 228 | result, err = tidis.db.BatchWithTxn(f, txn) 229 | } 230 | 231 | if err != nil { 232 | return false, err 233 | } 234 | 235 | return result.(bool), err 236 | 237 | } 238 | 239 | func (tidis *Tidis) Setex(dbId uint8, key []byte, sec int64, value []byte) error { 240 | if len(key) == 0 { 241 | return terror.ErrKeyEmpty 242 | } 243 | f := func(txn interface{}) (interface{}, error) { 244 | return nil, tidis.SetexWithTxn(dbId, txn, key, sec, value) 245 | } 246 | 247 | // execute in txn 248 | _, err := tidis.db.BatchInTxn(f) 249 | 250 | return err 251 | } 252 | 253 | func (tidis *Tidis) SetexWithTxn(dbId uint8, txn interface{}, key []byte, sec int64, value []byte) error { 254 | if len(key) == 0 { 255 | return terror.ErrKeyEmpty 256 | } 257 | 258 | obj := StringObj{ 259 | Object: Object{ 260 | Type: TSTRING, 261 | Tomb: 0, 262 | ExpireAt: utils.Now() + uint64(sec)*1000, 263 | }, 264 | Value: value, 265 | } 266 | value = MarshalStringObj(&obj) 267 | f := func(txn interface{}) (interface{}, error) { 268 | err := tidis.Set(dbId, txn, key, value) 269 | if err != nil { 270 | return nil, err 271 | } 272 | return nil, nil 273 | } 274 | 275 | _, err := tidis.db.BatchWithTxn(f, txn) 276 | 277 | return err 278 | } 279 | 280 | func (tidis *Tidis) MSet(dbId uint8, txn interface{}, keyvals [][]byte) (int, error) { 281 | if len(keyvals) == 0 { 282 | return 0, terror.ErrKeyEmpty 283 | } 284 | 285 | kvm := make(map[string][]byte, len(keyvals)) 286 | for i := 0; i < len(keyvals)-1; i += 2 { 287 | k := string(tidis.RawKeyPrefix(dbId, keyvals[i])) 288 | obj := StringObj{ 289 | Object: Object{ 290 | Type: TSTRING, 291 | Tomb: 0, 292 | ExpireAt: 0, 293 | }, 294 | Value: keyvals[i+1], 295 | } 296 | v := MarshalStringObj(&obj) 297 | kvm[k] = v 298 | } 299 | if txn == nil { 300 | return tidis.db.MSet(kvm) 301 | } 302 | return tidis.db.MSetWithTxn(kvm, txn) 303 | } 304 | 305 | // Delete is a generic api for all type keys 306 | func (tidis *Tidis) Delete(dbId uint8, txn interface{}, keys [][]byte) (int, error) { 307 | if len(keys) == 0 { 308 | return 0, terror.ErrKeyEmpty 309 | } 310 | 311 | nkeys := make([][]byte, len(keys)) 312 | for i := 0; i < len(keys); i++ { 313 | nkeys[i] = tidis.RawKeyPrefix(dbId, keys[i]) 314 | } 315 | 316 | var ( 317 | ret interface{} 318 | err error 319 | 320 | ) 321 | 322 | // check object type 323 | f := func(txn interface{}) (interface{}, error) { 324 | var deleted int = 0 325 | for idx, key := range nkeys { 326 | metaValue, err := tidis.db.GetWithTxn(key, txn) 327 | if err != nil { 328 | return 0, err 329 | } 330 | if metaValue == nil { 331 | return 0, nil 332 | } 333 | objType := metaValue[0] 334 | switch objType { 335 | case TSTRING: 336 | ret, err = tidis.db.DeleteWithTxn([][]byte{key}, txn) 337 | deleted++ 338 | case THASHMETA: 339 | var hasDeleted uint8 340 | hasDeleted, err = tidis.HclearWithTxn(dbId, txn, keys[idx]) 341 | if hasDeleted == 1 { 342 | deleted++ 343 | } 344 | case TLISTMETA: 345 | var deleteCount int 346 | deleteCount, err = tidis.LdelWithTxn(dbId, txn, keys[idx]) 347 | if deleteCount > 0 { 348 | deleted++ 349 | } 350 | case TSETMETA: 351 | var deleteCount int 352 | deleteCount, err = tidis.SclearKeyWithTxn(dbId, txn, keys[idx]) 353 | if deleteCount > 0 { 354 | deleted++ 355 | } 356 | case TZSETMETA: 357 | var deleteCount uint64 358 | deleteCount, err = tidis.ZremrangebyscoreWithTxn(dbId, txn, keys[idx], ScoreMin, ScoreMax) 359 | if deleteCount > 0 { 360 | deleted++ 361 | } 362 | } 363 | } 364 | return deleted, err 365 | } 366 | 367 | if txn == nil { 368 | ret, err = tidis.db.BatchInTxn(f) 369 | } else { 370 | ret, err = tidis.db.BatchWithTxn(f, txn) 371 | } 372 | if err != nil { 373 | return 0, err 374 | } 375 | 376 | return ret.(int), nil 377 | } 378 | 379 | func (tidis *Tidis) Incr(dbId uint8, key []byte, step int64) (int64, error) { 380 | if len(key) == 0 { 381 | return 0, terror.ErrKeyEmpty 382 | } 383 | 384 | // inner func for tikv backend 385 | f := func(txn interface{}) (interface{}, error) { 386 | return tidis.IncrWithTxn(dbId, txn, key, step) 387 | } 388 | 389 | // execute in txn 390 | ret, err := tidis.db.BatchInTxn(f) 391 | if err != nil { 392 | return 0, err 393 | } 394 | 395 | retInt, ok := ret.(int64) 396 | if !ok { 397 | return 0, terror.ErrTypeAssertion 398 | } 399 | return retInt, nil 400 | } 401 | 402 | func (tidis *Tidis) IncrWithTxn(dbId uint8, txn interface{}, key []byte, step int64) (int64, error) { 403 | if len(key) == 0 { 404 | return 0, terror.ErrKeyEmpty 405 | } 406 | 407 | metaKey := tidis.RawKeyPrefix(dbId, key) 408 | 409 | // inner func for tikv backend 410 | f := func(txn1 interface{}) (interface{}, error) { 411 | var ( 412 | ev []byte 413 | dv int64 414 | ) 415 | 416 | txn, ok := txn1.(kv.Transaction) 417 | if !ok { 418 | return nil, terror.ErrBackendType 419 | } 420 | 421 | // get from db 422 | objType, obj, err := tidis.GetObject(dbId, txn, key) 423 | if err != nil { 424 | return 0, err 425 | } 426 | if obj == nil { 427 | obj = &StringObj{ 428 | Object: Object{ 429 | Type: TSTRING, 430 | Tomb: 0, 431 | ExpireAt: 0, 432 | }, 433 | Value: nil, 434 | } 435 | } 436 | if objType != TSTRING { 437 | return nil, terror.ErrNotInteger 438 | } 439 | strObj := obj.(*StringObj) 440 | 441 | if strObj.Value == nil { 442 | dv = 0 443 | } else { 444 | dv, err = util.StrBytesToInt64(strObj.Value) 445 | if err != nil { 446 | return nil, terror.ErrNotInteger 447 | } 448 | } 449 | // incr by step 450 | dv = dv + step 451 | 452 | ev, _ = util.Int64ToStrBytes(dv) 453 | // update object 454 | strObj.Value = ev 455 | // marshal object to bytes 456 | rawValue := MarshalStringObj(strObj) 457 | err = txn.Set(metaKey, rawValue) 458 | if err != nil { 459 | return nil, err 460 | } 461 | return dv, nil 462 | } 463 | 464 | // execute in txn 465 | ret, err := tidis.db.BatchWithTxn(f, txn) 466 | if err != nil { 467 | return 0, err 468 | } 469 | 470 | retInt, ok := ret.(int64) 471 | if !ok { 472 | return 0, terror.ErrTypeAssertion 473 | } 474 | return retInt, nil 475 | } 476 | 477 | func (tidis *Tidis) Decr(dbId uint8, key []byte, step int64) (int64, error) { 478 | return tidis.Incr(dbId, key, -1*step) 479 | } 480 | 481 | func (tidis *Tidis) DecrWithTxn(dbId uint8, txn interface{}, key []byte, step int64) (int64, error) { 482 | return tidis.IncrWithTxn(dbId, txn, key, -1*step) 483 | } 484 | 485 | // expire is also a series generic commands for all kind type keys 486 | func (tidis *Tidis) PExpireAt(dbId uint8, key []byte, ts int64) (int, error) { 487 | if len(key) == 0 || ts < 0 { 488 | return 0, terror.ErrCmdParams 489 | } 490 | 491 | f := func(txn interface{}) (interface{}, error) { 492 | return tidis.PExpireAtWithTxn(dbId, txn, key, ts) 493 | } 494 | 495 | // execute txn 496 | v, err := tidis.db.BatchInTxn(f) 497 | if err != nil { 498 | return 0, err 499 | } 500 | 501 | return v.(int), nil 502 | } 503 | 504 | func (tidis *Tidis) PExpireAtWithTxn(dbId uint8, txn interface{}, key []byte, ts int64) (int, error) { 505 | if len(key) == 0 || ts < 0 { 506 | return 0, terror.ErrCmdParams 507 | } 508 | 509 | f := func(txn1 interface{}) (interface{}, error) { 510 | txn, ok := txn1.(kv.Transaction) 511 | if !ok { 512 | return 0, terror.ErrBackendType 513 | } 514 | var ( 515 | err error 516 | obj IObject 517 | ) 518 | 519 | // check key exists 520 | _, obj, err = tidis.GetObject(dbId, txn, key) 521 | if err != nil { 522 | return 0, err 523 | } 524 | if obj == nil { 525 | // not exists 526 | return 0, nil 527 | } 528 | metaKey := tidis.RawKeyPrefix(dbId, key) 529 | if obj.ObjectExpired(utils.Now()) { 530 | // TODO delete data key 531 | tidis.Delete(dbId, txn, [][]byte{key}) 532 | return 0, nil 533 | } 534 | 535 | // update expireAt 536 | obj.SetExpireAt(uint64(ts)) 537 | metaValue := MarshalObj(obj) 538 | 539 | err = txn.Set(metaKey, metaValue) 540 | if err != nil { 541 | return 0, err 542 | } 543 | return 1, nil 544 | } 545 | 546 | // execute txn 547 | v, err := tidis.db.BatchWithTxn(f, txn) 548 | if err != nil { 549 | return 0, err 550 | } 551 | 552 | return v.(int), nil 553 | } 554 | 555 | func (tidis *Tidis) PExpire(dbId uint8, key []byte, ms int64) (int, error) { 556 | return tidis.PExpireAt(dbId, key, ms+(time.Now().UnixNano()/1000/1000)) 557 | } 558 | 559 | func (tidis *Tidis) ExpireAt(dbId uint8, key []byte, ts int64) (int, error) { 560 | return tidis.PExpireAt(dbId, key, ts*1000) 561 | } 562 | 563 | func (tidis *Tidis) Expire(dbId uint8, key []byte, s int64) (int, error) { 564 | return tidis.PExpire(dbId, key, s*1000) 565 | } 566 | 567 | func (tidis *Tidis) PExpireWithTxn(dbId uint8, txn interface{}, key []byte, ms int64) (int, error) { 568 | return tidis.PExpireAtWithTxn(dbId, txn, key, ms+(time.Now().UnixNano()/1000/1000)) 569 | } 570 | 571 | func (tidis *Tidis) ExpireAtWithTxn(dbId uint8, txn interface{}, key []byte, ts int64) (int, error) { 572 | return tidis.PExpireAtWithTxn(dbId, txn, key, ts*1000) 573 | } 574 | 575 | func (tidis *Tidis) ExpireWithTxn(dbId uint8, txn interface{}, key []byte, s int64) (int, error) { 576 | return tidis.PExpireWithTxn(dbId, txn, key, s*1000) 577 | } 578 | 579 | // generic command 580 | func (tidis *Tidis) PTtl(dbId uint8, txn interface{}, key []byte) (int64, error) { 581 | if len(key) == 0 { 582 | return 0, terror.ErrKeyEmpty 583 | } 584 | var ttl int64 585 | 586 | now := utils.Now() 587 | 588 | _, obj, err := tidis.GetObject(dbId, txn, key) 589 | if err != nil { 590 | return 0, err 591 | } 592 | if obj == nil { 593 | return -2, nil 594 | } 595 | 596 | if !obj.IsExpireSet() { 597 | ttl = -1 598 | } else if !obj.ObjectExpired(now) { 599 | ttl = int64(obj.TTL(now)) 600 | } else { 601 | ttl = 0 602 | tidis.Delete(dbId, txn, [][]byte{key}) 603 | } 604 | 605 | return ttl, nil 606 | } 607 | 608 | func (tidis *Tidis) Ttl(dbId uint8, txn interface{}, key []byte) (int64, error) { 609 | ttl, err := tidis.PTtl(dbId, txn, key) 610 | if ttl < 0 { 611 | return ttl, err 612 | } 613 | return ttl / 1000, err 614 | } 615 | 616 | func (tidis *Tidis) Type(dbId uint8, txn interface{}, key []byte) (string, error) { 617 | if len(key) == 0 { 618 | return "", terror.ErrKeyEmpty 619 | } 620 | 621 | t, _, err := tidis.GetObject(dbId, txn, key) 622 | if err != nil { 623 | return "", err 624 | } 625 | switch t { 626 | case TSTRING: 627 | return "string", nil 628 | case TLISTMETA: 629 | return "list", nil 630 | case TZSETMETA: 631 | return "zset", nil 632 | case TSETMETA: 633 | return "set", nil 634 | case THASHMETA: 635 | return "hash", nil 636 | default: 637 | return "unknown", nil 638 | } 639 | 640 | } 641 | -------------------------------------------------------------------------------- /tidis/tidis.go: -------------------------------------------------------------------------------- 1 | // 2 | // tidis.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | // wrapper for kv storage engine operation 11 | 12 | import ( 13 | "github.com/google/uuid" 14 | "github.com/pingcap/tidb/kv" 15 | "github.com/yongman/go/log" 16 | "github.com/yongman/go/util" 17 | "sync" 18 | "time" 19 | 20 | "github.com/deckarep/golang-set" 21 | "github.com/yongman/tidis/config" 22 | "github.com/yongman/tidis/store" 23 | ) 24 | 25 | type Tidis struct { 26 | uuid uuid.UUID 27 | conf *config.Config 28 | db store.DB 29 | 30 | wLock sync.RWMutex 31 | Lock sync.Mutex 32 | wg sync.WaitGroup 33 | 34 | asyncDelCh chan AsyncDelItem 35 | asyncDelSet mapset.Set 36 | } 37 | 38 | func NewTidis(conf *config.Config) (*Tidis, error) { 39 | var err error 40 | 41 | tidis := &Tidis{ 42 | uuid: uuid.New(), 43 | conf: conf, 44 | asyncDelCh: make(chan AsyncDelItem, 10240), 45 | asyncDelSet: mapset.NewSet(), 46 | } 47 | tidis.db, err = store.Open(conf) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return tidis, nil 53 | } 54 | 55 | func (tidis *Tidis) Close() error { 56 | err := tidis.db.Close() 57 | if err != nil { 58 | return err 59 | } 60 | return nil 61 | } 62 | 63 | func (tidis *Tidis) NewTxn() (interface{}, error) { 64 | return tidis.db.NewTxn() 65 | } 66 | 67 | func (tidis *Tidis) TenantId() string { 68 | return tidis.conf.Tidis.TenantId 69 | } 70 | 71 | func (tidis *Tidis) RawKeyPrefix(dbid uint8, key []byte) []byte { 72 | return RawKeyPrefix(tidis.TenantId(), dbid, key) 73 | } 74 | 75 | func (tidis *Tidis) RunGC(safePoint uint64, concurrency int) error { 76 | return tidis.db.RunGC(safePoint, concurrency) 77 | } 78 | 79 | func (tidis *Tidis) IsLeader() bool { 80 | leaderKey := RawSysLeaderKey() 81 | val, err := tidis.db.Get(leaderKey) 82 | if err != nil { 83 | return false 84 | } 85 | 86 | // val should be in format uuid(36 bytes)+time(8 bytes) 87 | if val == nil || len(val) < 44 { 88 | return false 89 | } 90 | 91 | uuid, tsBytes := val[:36], val[36:] 92 | 93 | now := time.Now().UTC() 94 | ts, err := util.BytesToInt64(tsBytes) 95 | if err != nil { 96 | return false 97 | } 98 | tsTime := time.Unix(ts, 0).UTC() 99 | 100 | if string(uuid) == tidis.uuid.String() && now.Sub(tsTime) < 101 | time.Duration(tidis.conf.Tidis.LeaderLeaseDuration) * time.Second { 102 | return true 103 | } 104 | return false 105 | } 106 | 107 | func (tidis *Tidis) CheckLeader(leaderLeaseDuration int) { 108 | // check leader lease timeout, release if needed 109 | // 1. check is leader 110 | // 2. check leader and lease time out 111 | // 3. try to be leader and write lease uuid and time 112 | f := func(txn interface{}) (interface{}, error) { 113 | leaderKey := RawSysLeaderKey() 114 | val, err := tidis.db.GetWithTxn(leaderKey, txn) 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | if len(val) == 44 { 120 | uuid, tsBytes := val[:36], val[36:] 121 | 122 | now := time.Now().UTC() 123 | ts, err := util.BytesToInt64(tsBytes) 124 | if err != nil { 125 | return nil, err 126 | } 127 | tsTime := time.Unix(ts, 0).UTC() 128 | 129 | if now.Sub(tsTime) < time.Duration(leaderLeaseDuration) * time.Second { 130 | if string(uuid) == tidis.uuid.String() { 131 | log.Infof("I am already leader, renew lease with uuid %s and timestamp %d", uuid, ts) 132 | err = tidis.renewLeader(txn) 133 | if err != nil { 134 | return false, err 135 | } 136 | return true, nil 137 | } else { 138 | log.Infof("leader already exists in lease duration, stay in follower") 139 | return false, nil 140 | } 141 | } 142 | } 143 | 144 | // renew lease with my uuid and timestamp 145 | err = tidis.renewLeader(txn) 146 | if err != nil { 147 | return false, err 148 | } 149 | return true, nil 150 | } 151 | 152 | log.Infof("check leader lease with uuid %s", tidis.uuid) 153 | isLeader, err := tidis.db.BatchInTxn(f) 154 | if err != nil { 155 | log.Errorf("check leader and renew lease failed, error: %s", err.Error()) 156 | } 157 | if isLeader.(bool) { 158 | log.Infof("I am leader with new release") 159 | } 160 | } 161 | 162 | func (tidis *Tidis) renewLeader(txn interface{}) error { 163 | leaderKey := RawSysLeaderKey() 164 | now := time.Now().UTC().Unix() 165 | tsBytes, _ := util.Int64ToBytes(now) 166 | 167 | log.Infof("try to renew lease with uuid %s and timestamp %d", tidis.uuid, now) 168 | 169 | val := append([]byte(tidis.uuid.String()), tsBytes...) 170 | 171 | txn1, _ := txn.(kv.Transaction) 172 | return txn1.Set(leaderKey, val) 173 | } 174 | -------------------------------------------------------------------------------- /tidis/ttl.go: -------------------------------------------------------------------------------- 1 | // 2 | // ttl.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | import ( 11 | "time" 12 | ) 13 | 14 | // ttl for user key checker and operater 15 | 16 | type ttlChecker struct { 17 | dataType byte 18 | maxPerLoop int 19 | interval int 20 | tdb *Tidis 21 | } 22 | 23 | func NewTTLChecker(datatype byte, max, interval int, tdb *Tidis) *ttlChecker { 24 | return &ttlChecker{ 25 | dataType: datatype, 26 | maxPerLoop: max, 27 | interval: interval, 28 | tdb: tdb, 29 | } 30 | } 31 | 32 | func (ch *ttlChecker) Run() { 33 | c := time.Tick(time.Duration(ch.interval) * time.Millisecond) 34 | for _ = range c { 35 | switch ch.dataType { 36 | case TSTRING: 37 | 38 | case THASHMETA: 39 | 40 | case TLISTMETA: 41 | 42 | case TSETMETA: 43 | case TZSETMETA: 44 | } 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tidis/type.go: -------------------------------------------------------------------------------- 1 | // 2 | // type.go 3 | // Copyright (C) 2018 YanMing 4 | // 5 | // Distributed under terms of the MIT license. 6 | // 7 | 8 | package tidis 9 | 10 | const ( 11 | TSTRING byte = iota 12 | TLISTMETA 13 | TLISTDATA 14 | THASHMETA 15 | THASHDATA 16 | TSETMETA 17 | TSETDATA 18 | TZSETMETA 19 | TZSETSCORE 20 | TZSETDATA 21 | TTTLMETA 22 | TTTLDATA 23 | ) 24 | 25 | const ( 26 | FNORMAL byte = iota 27 | FDELETED 28 | ) 29 | 30 | const ( 31 | ObjectData byte = iota 32 | ObjectTTL 33 | ) 34 | 35 | const ( 36 | MetaTypeKey byte = iota 37 | DataTypeKey 38 | ScoreTypeKey 39 | ) 40 | 41 | var ( 42 | EmptyListOrSet []interface{} = make([]interface{}, 0) 43 | ) 44 | -------------------------------------------------------------------------------- /utils/time.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "time" 4 | 5 | func Now() uint64 { 6 | return uint64(time.Now().UnixNano() / 1000 / 1000) 7 | } 8 | --------------------------------------------------------------------------------