├── .github └── workflows │ ├── go.yml │ └── reviewdog.yml ├── .gitignore ├── LICENSE ├── README-CN.md ├── README.md ├── example ├── BargainRush │ └── BargainRush.go ├── BitCount │ └── BitCount.go ├── BloomFilter │ └── BloomFilter.go ├── BoundedCounter │ └── BoundedCounter.go ├── CarTrack │ └── CarTrack.go ├── CpuCurve │ └── CpuCurve.go ├── CrawlerSystem │ └── CrawlerSystem.go ├── DeviceLogin │ └── DeviceLogin.go ├── FraudPrevention │ └── FraudPrevention.go ├── FullTextSearch │ └── FullTextSearch.go ├── GamePackage │ └── GamePackage.go ├── JSONDocument │ └── JSONDocument.go ├── LbsBuy │ └── LbsBuy.go ├── LeaderBoard │ └── LeaderBoard.go ├── MultiIndexSearch │ └── MultiIndexSearch.go ├── PasswordExpire │ └── PasswordExpire.go ├── example.go ├── taircluster │ └── example_taircluster.go ├── tairhash │ └── example_hash.go ├── tairstring │ └── distribute_lock.go └── tairvector │ └── example_vector.go ├── go.mod ├── go.sum └── tair ├── arg.go ├── main_test.go ├── tair.go ├── tairbloom.go ├── tairbloom_test.go ├── taircluster.go ├── taircluster_test.go ├── taircommands.go ├── taircpc.go ├── taircpc_test.go ├── tairdoc.go ├── tairdoc_test.go ├── tairgis.go ├── tairgis_test.go ├── tairhash.go ├── tairhash_test.go ├── tairpipeline.go ├── tairpipeline_test.go ├── tairroaring.go ├── tairroaring_test.go ├── tairsearch.go ├── tairsearch_test.go ├── tairstring.go ├── tairstring_test.go ├── tairts.go ├── tairts_test.go ├── tairvector.go ├── tairvector_test.go ├── tairzset.go └── tairzset_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | test-ubuntu-with-redis-7: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: clone and make redis 16 | run: | 17 | sudo apt-get install git 18 | git clone https://github.com/redis/redis 19 | cd redis 20 | git checkout 7.2.5 21 | make -j 22 | 23 | - name: clone and make tairhash 24 | run: | 25 | git clone https://github.com/alibaba/TairHash.git 26 | cd TairHash 27 | mkdir build 28 | cd build 29 | cmake ../ 30 | make -j 31 | 32 | - name: clone and make tairstring 33 | run: | 34 | git clone https://github.com/alibaba/TairString.git 35 | cd TairString 36 | mkdir build 37 | cd build 38 | cmake ../ 39 | make -j 40 | 41 | - name: clone and make tairzset 42 | run: | 43 | git clone https://github.com/alibaba/TairZset.git 44 | cd TairZset 45 | mkdir build 46 | cd build 47 | cmake ../ 48 | make -j 49 | 50 | - name: start redis and redis cluster with loadmodule 51 | run: | 52 | work_path=$(pwd) 53 | tairhash_path=${work_path}/TairHash/lib/tairhash_module.so 54 | tairstring_path=${work_path}/TairString/lib/tairstring_module.so 55 | tairzset_path=${work_path}/TairZset/lib/tairzset_module.so 56 | echo "loadmodule ${tairhash_path}" >> redis/redis.conf 57 | echo "loadmodule ${tairstring_path}" >> redis/redis.conf 58 | echo "loadmodule ${tairzset_path}" >> redis/redis.conf 59 | ./redis/src/redis-server redis/redis.conf & 60 | 61 | # start redis cluster 62 | cd redis/utils/create-cluster 63 | OPTIONS="--loadmodule ${tairhash_path} --loadmodule ${tairstring_path} --loadmodule ${tairzset_path}" 64 | echo ADDITIONAL_OPTIONS="'${OPTIONS}'" > config.sh 65 | cat config.sh 66 | ./create-cluster start 67 | echo yes|./create-cluster create 68 | 69 | - name: Set up Go 70 | uses: actions/setup-go@v3 71 | with: 72 | go-version: 1.18 73 | 74 | - name: Build 75 | run: go build -v ./... 76 | 77 | - name: Test 78 | run: | 79 | go test -v ./... -run TestTairStringTestSuite 80 | go test -v ./... -run TestTairHashTestSuite 81 | go test -v ./... -run TestTairZsetTestSuite 82 | go test -v ./... -run TestTairPipelineTestSuite 83 | 84 | -------------------------------------------------------------------------------- /.github/workflows/reviewdog.yml: -------------------------------------------------------------------------------- 1 | name: reviewdog 2 | on: [pull_request] 3 | jobs: 4 | golangci-lint: 5 | name: runner 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Check out code into the Go module directory 9 | uses: actions/checkout@v3 10 | 11 | - name: golangci-lint 12 | uses: reviewdog/action-golangci-lint@v2 13 | 14 | - name: Setup reviewdog 15 | uses: reviewdog/action-setup@v1 16 | 17 | - name: gofumpt -s with reviewdog 18 | env: 19 | REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | run: | 21 | go install mvdan.cc/gofumpt@v0.2.0 22 | gofumpt -e -d . | \ 23 | reviewdog -name="gofumpt" -f=diff -f.diff.strip=0 -reporter=github-pr-review 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij project files 2 | *.iml 3 | *.ipr 4 | *.iws 5 | .idea/ 6 | .vscode/ 7 | 8 | # Binaries for programs and plugins 9 | *.exe 10 | *.exe~ 11 | *.dll 12 | *.so 13 | *.dylib 14 | *.coverprofile 15 | 16 | # Test binary, built with `go test -c` 17 | *.test 18 | 19 | # Output of the go coverage tool, specifically when used with LiteIDE 20 | *.out 21 | 22 | # Dependency directories (remove the comment below to include it) 23 | # vendor/ 24 | 25 | main_test.go 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alibaba 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 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # tair-go 2 | 3 | ![build workflow](https://github.com/alibaba/tair-go/actions/workflows/go.yml/badge.svg) 4 | [![Go Reference](https://pkg.go.dev/badge/github.com/alibaba/tair-go.svg)](https://pkg.go.dev/github.com/alibaba/tair-go) 5 | 6 | English | [简体中文](./README-CN.md) 7 | 8 | 基于 [go-redis](https://github.com/go-redis/redis) 封装,用于操作 [Tair Modules](https://help.aliyun.com/document_detail/145957.html) 的客户端。 9 | 10 | - [TairHash](https://help.aliyun.com/document_detail/145970.html), 可实现 field 级别的过期。(已[开源](https://github.com/alibaba/TairHash)) 11 | - [TairString](https://help.aliyun.com/document_detail/145902.html), 支持 string 设置 version,增强的`cas`和`cad`命令可轻松实现分布式锁。(已[开源](https://github.com/alibaba/TairString)) 12 | - [TairZset](https://help.aliyun.com/document_detail/292812.html), 支持多维排序。(已[开源](https://github.com/alibaba/TairZset)) 13 | - [TairBloom](https://help.aliyun.com/document_detail/145972.html), 支持动态扩容的布隆过滤器。 14 | - [TairRoaring](https://help.aliyun.com/document_detail/311433.html), Roaring Bitmap, 使用少量的存储空间来实现海量数据的查询优化。 15 | - [TairSearch](https://help.aliyun.com/document_detail/417908.html), 支持ES-LIKE语法的全文索引和搜索模块。 16 | - [TairDoc](https://help.aliyun.com/document_detail/145940.html), 支持存储`JSON`类型。 17 | - [TairGis](https://help.aliyun.com/document_detail/145971.html), 支持地理位置点、线、面的相交、包含等关系判断。(已[开源](https://github.com/tair-opensource/TairGis)) 18 | - [TairTs](https://help.aliyun.com/document_detail/408954.html), 时序数据结构,提供低时延、高并发的内存读写访问。 19 | - [TairCpc](https://help.aliyun.com/document_detail/410587.html), 基于CPC(Compressed Probability Counting)压缩算法开发的数据结构,支持仅占用很小的内存空间对采样数据进行高性能计算。 20 | - [TairVector](https://help.aliyun.com/zh/tair/developer-reference/vector), 提供高性能、实时,集存储、检索于一体的向量数据库服务。 21 | 22 | ## 安装 23 | ``` 24 | go get github.com/alibaba/tair-go 25 | ``` 26 | 27 | ## 快速开始 28 | 一个 TairString 的示例如下所示: 29 | 30 | go.mod 31 | ``` 32 | require ( 33 | github.com/alibaba/tair-go v1.1.3 34 | ) 35 | ``` 36 | 37 | test.go 38 | ```Go 39 | import ( 40 | "context" 41 | "fmt" 42 | "github.com/redis/go-redis/v9" 43 | "github.com/alibaba/tair-go/tair" 44 | ) 45 | 46 | var ctx = context.Background() 47 | 48 | var tairClient *tair.TairClient 49 | 50 | func init() { 51 | tairClient = tair.NewTairClient(&redis.Options{ 52 | Addr: "xxx.redis.rds.aliyuncs.com:6379", 53 | Password: "xxx", 54 | DB: 0, 55 | }) 56 | } 57 | 58 | func main() { 59 | err := tairClient.ExSet(ctx, "exkey", "exval").Err() 60 | if err != nil { 61 | fmt.Println(err.Error()) 62 | } 63 | 64 | val, err := tairClient.ExGet(ctx, "exkey").Result() 65 | if err != nil { 66 | panic(err) 67 | } 68 | fmt.Println("get exkey values is: ", val) 69 | } 70 | ``` 71 | 72 | ## Tair 所有的 SDK 73 | 74 | | language | GitHub | 75 | |----------|---| 76 | | Java |https://github.com/alibaba/alibabacloud-tairjedis-sdk| 77 | | Python |https://github.com/alibaba/tair-py| 78 | | Go |https://github.com/alibaba/tair-go| 79 | | .Net |https://github.com/alibaba/AlibabaCloud.TairSDK| -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tair-go 2 | 3 | ![build workflow](https://github.com/alibaba/tair-go/actions/workflows/go.yml/badge.svg) 4 | [![Go Reference](https://pkg.go.dev/badge/github.com/alibaba/tair-go.svg)](https://pkg.go.dev/github.com/alibaba/tair-go) 5 | 6 | English | [简体中文](./README-CN.md) 7 | 8 | A client packaged based on [go-redis](https://github.com/go-redis/redis) that operates [Tair](https://www.alibabacloud.com/help/en/apsaradb-for-redis/latest/apsaradb-for-redis-enhanced-edition-overview) For Redis Modules. 9 | 10 | * [TairString](https://www.alibabacloud.com/help/en/tair/developer-reference/exstring), is a string that contains s version number.([Open sourced](https://github.com/alibaba/TairString)) 11 | * [TairHash](https://www.alibabacloud.com/help/en/tair/developer-reference/exhash), is a hash that allows you to specify the expiration time and version number of a field.([Open sourced](https://github.com/alibaba/TairHash)) 12 | * [TairZset](https://www.alibabacloud.com/help/en/tair/developer-reference/tairzset), allows you to sort data of the double type based on multiple dimensions. ([Open sourced](https://github.com/alibaba/TairZset)) 13 | * [TairBloom](https://www.alibabacloud.com/help/en/tair/developer-reference/bloom), is a Bloom filter that supports dynamic scaling. 14 | * [TairRoaring](https://www.alibabacloud.com/help/en/tair/developer-reference/roaring), is a more efficient and balanced type of compressed bitmaps recognized by the industry. 15 | * [TairSearch](https://www.alibabacloud.com/help/en/tair/developer-reference/search), is a full-text search module developed in-house based on Redis modules. 16 | * [TairDoc](https://www.alibabacloud.com/help/en/tair/developer-reference/doc), to perform create, read, update, and delete (CRUD) operations on JSON data. 17 | * [TairGis](https://www.alibabacloud.com/help/en/tair/developer-reference/gis), allowing you to query points, linestrings, and polygons. ([Open Sourced](https://github.com/tair-opensource/TairGis)) 18 | * [TairTs](https://www.alibabacloud.com/help/en/tair/developer-reference/ts), is a time series data structure that is developed on top of Redis modules. 19 | * [TairCpc](https://www.alibabacloud.com/help/en/tair/developer-reference/taircpc), is a data structure developed based on the compressed probability counting (CPC) sketch. 20 | * [TairVector](https://www.alibabacloud.com/help/en/tair/developer-reference/vector), is a vector that allows you to find similar data points in a high-dimensional vector space. 21 | 22 | ## Installation 23 | 24 | ``` 25 | go get github.com/alibaba/tair-go 26 | ``` 27 | 28 | ## Quickstart 29 | An example of TairString is as follows: 30 | 31 | go.mod 32 | ``` 33 | require ( 34 | github.com/alibaba/tair-go vx.x.x 35 | ) 36 | ``` 37 | 38 | test.go 39 | ```Go 40 | import ( 41 | "context" 42 | "fmt" 43 | "github.com/redis/go-redis/v9" 44 | "github.com/alibaba/tair-go/tair" 45 | ) 46 | 47 | var ctx = context.Background() 48 | 49 | var tairClient *tair.TairClient 50 | 51 | func init() { 52 | tairClient = tair.NewTairClient(&redis.Options{ 53 | Addr: "xxx.redis.rds.aliyuncs.com:6379", 54 | Password: "xxx", 55 | DB: 0, 56 | }) 57 | } 58 | 59 | func main() { 60 | err := tairClient.ExSet(ctx, "exkey", "exval").Err() 61 | if err != nil { 62 | fmt.Println(err.Error()) 63 | } 64 | 65 | val, err := tairClient.ExGet(ctx, "exkey").Result() 66 | if err != nil { 67 | panic(err) 68 | } 69 | fmt.Println("get exkey values is: ", val) 70 | } 71 | ``` 72 | 73 | ## Tair All SDK 74 | 75 | | language | GitHub | 76 | |----------|---| 77 | | Java |https://github.com/alibaba/alibabacloud-tairjedis-sdk| 78 | | Python |https://github.com/alibaba/tair-py| 79 | | Go |https://github.com/alibaba/tair-go| 80 | | .Net |https://github.com/alibaba/AlibabaCloud.TairSDK| 81 | -------------------------------------------------------------------------------- /example/BargainRush/BargainRush.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | type BargainRush struct { 24 | // field 25 | } 26 | 27 | func (l *BargainRush) bargainRush(key string, upperBound int64, lowerBound int64) bool { 28 | _, err := tairClient.ExIncrByArgs(ctx, key, -1, tair.ExIncrByArgs{}.New().Def(upperBound).Min(lowerBound)).Result() 29 | if err != nil { 30 | // process err 31 | //panic(err) 32 | return false 33 | } 34 | return true 35 | } 36 | 37 | func main() { 38 | key := "bargainRush" 39 | target := BargainRush{} 40 | size := 20 41 | for i := 0; i < size; i++ { 42 | fmt.Printf("attempt %v, result: %v\n", i, target.bargainRush(key, 10, 0)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/BitCount/BitCount.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | type BitCount struct { 24 | // field 25 | } 26 | 27 | func (l *BitCount) setBit(key string, offset int64, value int64) bool { 28 | _, err := tairClient.TrSetBit(ctx, key, offset, value).Result() 29 | if err != nil { 30 | // process err 31 | //panic(err) 32 | return false 33 | } 34 | return true 35 | 36 | } 37 | func (l *BitCount) bitCount(key string) int64 { 38 | result, err := tairClient.TrBitCount(ctx, key).Result() 39 | if err != nil { 40 | // process err 41 | //panic(err) 42 | return -1 43 | } 44 | return result 45 | } 46 | 47 | func main() { 48 | key := "BitCount" 49 | target := BitCount{} 50 | target.setBit(key, 0, 1) 51 | target.setBit(key, 1, 1) 52 | target.setBit(key, 2, 1) 53 | fmt.Println(target.bitCount(key)) 54 | } 55 | -------------------------------------------------------------------------------- /example/BloomFilter/BloomFilter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/alibaba/tair-go/tair" 9 | "github.com/redis/go-redis/v9" 10 | ) 11 | 12 | var ctx = context.Background() 13 | 14 | var tairClient *tair.TairClient 15 | 16 | func init() { 17 | tairClient = tair.NewTairClient(&redis.Options{ 18 | Addr: "***.redis.rds.aliyuncs.com:6379", 19 | Password: "xxx", 20 | DB: 0, 21 | }) 22 | } 23 | 24 | type BloomFilter struct { 25 | // field 26 | } 27 | 28 | func (l *BloomFilter) recommendedSystem(userId, docId string) { 29 | result, err := tairClient.BfExists(ctx, userId, docId).Result() 30 | if err != nil { 31 | // process err 32 | //panic(err) 33 | } 34 | if result { 35 | // do nothing 36 | } else { 37 | // recommend to user sendRecommendMsg(docid); 38 | // add userid with docid 39 | tairClient.BfAdd(ctx, userId, docId) 40 | } 41 | } 42 | 43 | func randStr(size int) string { 44 | str := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 45 | bytes := []byte(str) 46 | var result []byte 47 | rand.Seed(time.Now().UnixNano() + int64(rand.Intn(100000))) 48 | for i := 0; i < size; i++ { 49 | result = append(result, bytes[rand.Intn(len(bytes))]) 50 | } 51 | return string(result) 52 | } 53 | func main() { 54 | key := "BloomFilter" 55 | target := BloomFilter{} 56 | target.recommendedSystem(key, randStr(10)) 57 | target.recommendedSystem(key, randStr(10)) 58 | target.recommendedSystem(key, randStr(10)) 59 | target.recommendedSystem(key, randStr(10)) 60 | 61 | } 62 | -------------------------------------------------------------------------------- /example/BoundedCounter/BoundedCounter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | type BoundedCounter struct { 24 | // field 25 | } 26 | 27 | func (l *BoundedCounter) tryAcquire(key string, upperBoud int64, interval int64) bool { 28 | a := make([]string, 0) 29 | strings := append(a, key) 30 | _, err := tairClient.Eval(ctx, "if redis.call('exists', KEYS[1]) == 1 then return redis.call('EXINCRBY', KEYS[1], '1', 'MAX', ARGV[1], 'KEEPTTL')"+ 31 | " else return redis.call('EXSET', KEYS[1], 0, 'EX', ARGV[2]) end", strings, upperBoud, interval).Result() 32 | if err != nil { 33 | // process err 34 | //panic(err) 35 | return true 36 | } 37 | return false 38 | } 39 | 40 | func main() { 41 | key := "rateLimiter" 42 | target := BoundedCounter{} 43 | size := 10 44 | for i := 0; i < size; i++ { 45 | fmt.Printf("attempt %v, result: %v\n", i, target.tryAcquire(key, 8, 10)) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/CarTrack/CarTrack.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/alibaba/tair-go/tair" 10 | "github.com/redis/go-redis/v9" 11 | ) 12 | 13 | var ctx = context.Background() 14 | 15 | var tairClient *tair.TairClient 16 | 17 | func init() { 18 | tairClient = tair.NewTairClient(&redis.Options{ 19 | Addr: "***.redis.rds.aliyuncs.com:6379", 20 | Password: "xxx", 21 | DB: 0, 22 | }) 23 | } 24 | 25 | type CarTrack struct { 26 | // field 27 | } 28 | 29 | func (l *CarTrack) addCoordinate(key, ts string, longitude, latitude float64) bool { 30 | result, err := tairClient.GisAdd(ctx, key, ts, "POINT ("+strconv.FormatFloat(longitude, 'E', -1, 64)+" "+strconv.FormatFloat(latitude, 'E', -1, 64)+")").Result() 31 | if err != nil { 32 | // process err 33 | //panic(err) 34 | } 35 | if result == 1 { 36 | return true 37 | } 38 | return false 39 | } 40 | 41 | func (l *CarTrack) getAllCoordinate(key string) map[string]string { 42 | result, err := tairClient.GisGetAll(ctx, key).Result() 43 | if err != nil { 44 | // process err 45 | //panic(err) 46 | return nil 47 | } 48 | return result 49 | } 50 | 51 | func main() { 52 | key := "CarTrack" 53 | target := CarTrack{} 54 | target.addCoordinate(key, strconv.Itoa(int(time.Now().Unix())), 120.036188, 30.287922) 55 | time.Sleep(1 * time.Millisecond) 56 | target.addCoordinate(key, strconv.Itoa(int(time.Now().Unix())), 120.037625, 30.292225) 57 | time.Sleep(1 * time.Millisecond) 58 | target.addCoordinate(key, strconv.Itoa(int(time.Now().Unix())), 120.034435, 30.303303) 59 | fmt.Println(target.getAllCoordinate(key)) 60 | } 61 | -------------------------------------------------------------------------------- /example/CpuCurve/CpuCurve.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/alibaba/tair-go/tair" 7 | "github.com/redis/go-redis/v9" 8 | ) 9 | 10 | var ctx = context.Background() 11 | 12 | var tairClient *tair.TairClient 13 | 14 | func init() { 15 | tairClient = tair.NewTairClient(&redis.Options{ 16 | Addr: "***.redis.rds.aliyuncs.com:6379", 17 | Password: "xxx", 18 | DB: 0, 19 | }) 20 | } 21 | 22 | type CpuCurve struct { 23 | // field 24 | } 25 | 26 | func (l *CpuCurve) addPoint(ip, ts string, value float64) bool { 27 | result, err := tairClient.ExTsAdd(ctx, "CPU_LOAD", ip, ts, value).Result() 28 | if err != nil { 29 | // process err 30 | //panic(err) 31 | } 32 | if "OK" == result { 33 | return true 34 | } 35 | return false 36 | } 37 | 38 | func (l *CpuCurve) rangePoint(ip, startTs, endTs string) *tair.ExTsSKeyCmd { 39 | result, err := tairClient.ExTsRange(ctx, "CPU_LOAD", ip, startTs, endTs).Result() 40 | if err != nil { 41 | // process err 42 | //panic(err) 43 | return nil 44 | } 45 | return result 46 | } 47 | 48 | func main() { 49 | target := CpuCurve{} 50 | target.addPoint("127.0.0.1", "*", 10) 51 | target.addPoint("127.0.0.1", "*", 20) 52 | target.addPoint("127.0.0.1", "*", 30) 53 | target.rangePoint("127.0.0.1", "1587889046161", "*") 54 | } 55 | -------------------------------------------------------------------------------- /example/CrawlerSystem/CrawlerSystem.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | type CrawlerSystem struct { 24 | } 25 | 26 | func (l *CrawlerSystem) JudeUrlsExists(key string, urls ...string) []bool { 27 | result, err := tairClient.BfMExists(ctx, key, urls...).Result() 28 | if err != nil { 29 | // process err 30 | //panic(err) 31 | return nil 32 | } 33 | return result 34 | 35 | } 36 | 37 | func main() { 38 | key := "CrawlerSystem" 39 | target := CrawlerSystem{} 40 | tairClient.BfAdd(ctx, key, "abc") 41 | tairClient.BfAdd(ctx, key, "def") 42 | tairClient.BfAdd(ctx, key, "ghi") 43 | fmt.Println(target.JudeUrlsExists(key, "abc", "def", "xxx")) 44 | } 45 | -------------------------------------------------------------------------------- /example/DeviceLogin/DeviceLogin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/alibaba/tair-go/tair" 10 | "github.com/redis/go-redis/v9" 11 | ) 12 | 13 | var ctx = context.Background() 14 | 15 | var tairClient *tair.TairClient 16 | 17 | func init() { 18 | tairClient = tair.NewTairClient(&redis.Options{ 19 | Addr: "***.redis.rds.aliyuncs.com:6379", 20 | Password: "xxx", 21 | DB: 0, 22 | }) 23 | } 24 | 25 | type DeviceLogin struct { 26 | // field 27 | } 28 | 29 | func (d *DeviceLogin) DeviceLogin(key, loginTime, device string, timeout int64) bool { 30 | result, err := tairClient.ExHSetArgs(ctx, key, loginTime, device, tair.ExHSetArgs{}.New().Ex(time.Duration(timeout))).Result() 31 | if err != nil { 32 | // process err 33 | //panic(err) 34 | } 35 | if result == 1 { 36 | return true 37 | } 38 | return false 39 | } 40 | 41 | func main() { 42 | key := "DeviceLogin" 43 | login := DeviceLogin{} 44 | login.DeviceLogin(key, strconv.Itoa(int(time.Now().Unix())), "device1", 2) 45 | login.DeviceLogin(key, strconv.Itoa(int(time.Now().Unix())), "device2", 10) 46 | time.Sleep(5 * time.Second) 47 | fmt.Println(tairClient.ExHGetAll(ctx, key)) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /example/FraudPrevention/FraudPrevention.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | type FraudPrevention struct { 24 | // field 25 | } 26 | 27 | func (l *FraudPrevention) cpcAdd(key, item string) bool { 28 | result, err := tairClient.CpcUpdate(ctx, key, item).Result() 29 | if err != nil { 30 | // process err 31 | //panic(err) 32 | } 33 | if "OK" == result { 34 | return true 35 | } 36 | return false 37 | 38 | } 39 | func (l *FraudPrevention) cpcEstimate(key string) float64 { 40 | result, err := tairClient.CpcEstimate(ctx, key).Result() 41 | if err != nil { 42 | // process err 43 | //panic(err) 44 | 45 | } 46 | return result 47 | } 48 | 49 | func main() { 50 | key := "FraudPrevention" 51 | target := FraudPrevention{} 52 | target.cpcAdd(key, "a") 53 | target.cpcAdd(key, "b") 54 | target.cpcAdd(key, "c") 55 | fmt.Println(target.cpcEstimate(key)) 56 | target.cpcAdd(key, "d") 57 | fmt.Println(target.cpcEstimate(key)) 58 | } 59 | -------------------------------------------------------------------------------- /example/FullTextSearch/FullTextSearch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | type FullTextSearch struct { 24 | // field 25 | } 26 | 27 | func (l *FullTextSearch) createIndex(index, schema string) bool { 28 | _, err := tairClient.TftCreateIndex(ctx, index, schema).Result() 29 | if err != nil { 30 | // process err 31 | //panic(err) 32 | return true 33 | } 34 | return false 35 | 36 | } 37 | 38 | func (l *FullTextSearch) addDoc(index, doc string) string { 39 | result, err := tairClient.TftAddDoc(ctx, index, doc).Result() 40 | if err != nil { 41 | // process err 42 | //panic(err) 43 | return "" 44 | } 45 | return result 46 | } 47 | 48 | func (l *FullTextSearch) searchIndex(index, request string) string { 49 | result, err := tairClient.TftSearch(ctx, index, request).Result() 50 | if err != nil { 51 | // process err 52 | //panic(err) 53 | return "" 54 | } 55 | return result 56 | } 57 | 58 | func main() { 59 | key := "FullTextSearch" 60 | target := FullTextSearch{} 61 | 62 | target.createIndex(key, "{\"mappings\":{\"properties\":{\"title\":{\"type\":\"keyword\"},\"content\":{\"type\":\"text\",\"analyzer\":\"jieba\"},\"time\":{\"type\":\"long\"},\"author\":{\"type\":\"keyword\"},\"heat\":{\"type\":\"integer\"}}}}") 63 | target.addDoc(key, "{\"title\":\"Does not work\",\"content\":\"It was removed from the beta a while ago. You should have expected it was going to be removed from the stable client as well at some point.\",\"time\":1541713787,\"author\":\"cSg|mc\",\"heat\":10}") 64 | target.addDoc(key, "{\"title\":\"paypal no longer launches to purchase\",\"content\":\"Since the last update, I cannot purchase anything via the app. I just keep getting a screen that says\",\"time\":1551476987,\"author\":\"disasterpeac\",\"heat\":2}") 65 | target.addDoc(key, "{\"title\":\"cat not login\",\"content\":\"Hey! I am trying to login to steam beta client via qr code / steam guard code but both methods does not work for me\",\"time\":1664488187,\"author\":\"7xx\",\"heat\":100}") 66 | 67 | request := "{\"sort\":[{\"heat\":{\"order\":\"desc\"}}],\"query\":{\"match\":{\"content\":\"paypal work code\"}}}" 68 | fmt.Println(target.searchIndex(key, request)) 69 | } 70 | -------------------------------------------------------------------------------- /example/GamePackage/GamePackage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | type GamePackage struct { 24 | // field 25 | } 26 | 27 | func (l *GamePackage) addEquipment(key, packagePath, equipment string) int64 { 28 | result, err := tairClient.JsonArrAppendWithPath(ctx, key, packagePath, equipment).Result() 29 | if err != nil { 30 | // process err 31 | //panic(err) 32 | } 33 | return result 34 | } 35 | 36 | func main() { 37 | key := "GamePackage" 38 | target := GamePackage{} 39 | tairClient.JsonSet(ctx, key, ".", "[]") 40 | fmt.Println(target.addEquipment(key, ".", "\"lightsaber\"")) 41 | fmt.Println(target.addEquipment(key, ".", "\"howitzer\"")) 42 | fmt.Println(target.addEquipment(key, ".", "\"gun\"")) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /example/JSONDocument/JSONDocument.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | type JSONDocument struct { 24 | // field 25 | } 26 | 27 | func (l *JSONDocument) jsonSave(key, path, json string) bool { 28 | result, err := tairClient.JsonSet(ctx, key, path, json).Result() 29 | if err != nil { 30 | // process err 31 | //panic(err) 32 | } 33 | if result == "OK" { 34 | return true 35 | } 36 | return false 37 | } 38 | 39 | func (l *JSONDocument) jsonGet(key, path string) string { 40 | result, err := tairClient.JsonGetPath(ctx, key, path).Result() 41 | if err != nil { 42 | // process err 43 | //panic(err) 44 | } 45 | return result 46 | } 47 | 48 | func main() { 49 | key := "JSONDocument" 50 | target := JSONDocument{} 51 | target.jsonSave(key, ".", "{\"name\":\"tom\",\"age\":22,\"description\":\"A man with a blue "+ 52 | "lightsaber\",\"friends\":[]}") 53 | fmt.Println(target.jsonGet(key, ".description")) 54 | } 55 | -------------------------------------------------------------------------------- /example/LbsBuy/LbsBuy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | type LbsBuy struct { 24 | // field 25 | } 26 | 27 | func (l *LbsBuy) AddPolygon(key, storeName, storeWkt string) bool { 28 | result, err := tairClient.GisAdd(ctx, key, storeName, storeWkt).Result() 29 | if err != nil { 30 | // process err 31 | //panic(err) 32 | } 33 | if result == 1 { 34 | return true 35 | } 36 | return false 37 | } 38 | 39 | func (l *LbsBuy) GetServiceStore(key, userLocation string) map[string]string { 40 | result, err := tairClient.GisContains(ctx, key, userLocation).Result() 41 | if err != nil { 42 | panic(err.Error()) 43 | } 44 | return result 45 | 46 | } 47 | 48 | func main() { 49 | key := "LbsBuy" 50 | buy := LbsBuy{} 51 | buy.AddPolygon(key, "store-1", "POLYGON ((120.058897 30.283681, 120.093033 30.286363, 120.097632 30.269147, 120.050705 30.252863))") 52 | buy.AddPolygon(key, "store-2", "POLYGON ((120.026343 30.285739, 120.029289 30.280749, 120.0382 30.281997, 120.037051 30.288109))") 53 | 54 | fmt.Println(buy.GetServiceStore(key, "POINT(120.072264 30.27501)")) 55 | } 56 | -------------------------------------------------------------------------------- /example/LeaderBoard/LeaderBoard.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | type LeaderBoard struct { 24 | // field 25 | } 26 | 27 | func (l *LeaderBoard) addUser(key, member string, scores ...float64) bool { 28 | _, err := tairClient.ExZAddManyScore(ctx, key, member, scores...).Result() 29 | if err != nil { 30 | // process err 31 | //panic(err) 32 | return true 33 | } 34 | return false 35 | } 36 | 37 | func (l *LeaderBoard) top(key string, startOffSet int, endOffset int) []string { 38 | result, err := tairClient.ExZRevRange(ctx, key, startOffSet, endOffset).Result() 39 | if err != nil { 40 | // process err 41 | //panic(err) 42 | } 43 | return result 44 | 45 | } 46 | 47 | func main() { 48 | key := "LeaderBoard" 49 | target := LeaderBoard{} 50 | // add three user 51 | target.addUser(key, "user1", 20, 10, 30) 52 | target.addUser(key, "user2", 20, 15, 10) 53 | target.addUser(key, "user3", 30, 10, 20) 54 | // get top 2 55 | fmt.Println(target.top(key, 0, 1)) 56 | } 57 | -------------------------------------------------------------------------------- /example/MultiIndexSearch/MultiIndexSearch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "***.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | func main() { 24 | key := "MultiIndexSearch" 25 | tairClient.TftCreateIndex(ctx, key, "{\"mappings\":{\"properties\":{\"departure\":{\"type\":\"keyword\"},"+ 26 | "\"destination\":{\"type\":\"keyword\"},\"date\":{\"type\":\"keyword\"},\"seat\":{\"type\":\"keyword\"},"+ 27 | "\"with\":{\"type\":\"keyword\"},\"flight_id\":{\"type\":\"keyword\"},\"price\":{\"type\":\"double\"},"+ 28 | "\"departure_time\":{\"type\":\"long\"},\"destination_time\":{\"type\":\"long\"}}}}") 29 | 30 | tairClient.TftAddDoc(ctx, key, "{\"departure\":\"zhuhai\",\"destination\":\"hangzhou\",\"date\":\"2022-09-01\","+ 31 | "\"seat\":\"first\",\"with\":\"baby\",\"flight_id\":\"CZ1000\",\"price\":986.1,"+ 32 | "\"departure_time\":1661991010,\"destination_time\":1661998210}") 33 | 34 | request := "{\"sort\":[\"departure_time\"],\"query\":{\"bool\":{\"must\":[{\"term\":{\"date\":\"2022-09" + 35 | "-01\"}},{\"term\":{\"seat\":\"first\"}}]}}}" 36 | 37 | fmt.Println(tairClient.TftSearch(ctx, key, request)) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /example/PasswordExpire/PasswordExpire.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/alibaba/tair-go/tair" 9 | "github.com/redis/go-redis/v9" 10 | ) 11 | 12 | var ctx = context.Background() 13 | 14 | var tairClient *tair.TairClient 15 | 16 | func init() { 17 | tairClient = tair.NewTairClient(&redis.Options{ 18 | Addr: "***.redis.rds.aliyuncs.com:6379", 19 | Password: "xxx", 20 | DB: 0, 21 | }) 22 | } 23 | 24 | type PasswordExpire struct { 25 | // field 26 | } 27 | 28 | func (l *PasswordExpire) addUserPass(key string, user string, password string, timeout int64) bool { 29 | result, err := tairClient.ExHSetArgs(ctx, key, user, password, tair.ExHSetArgs{}.New().Ex(time.Duration(timeout))).Result() 30 | if err != nil { 31 | // process err 32 | //panic(err) 33 | } 34 | return result == 1 35 | } 36 | 37 | func main() { 38 | key := "PasswordExpire" 39 | target := PasswordExpire{} 40 | target.addUserPass(key, "user1", "pd1", 5) 41 | target.addUserPass(key, "user2", "pd2", 10) 42 | time.Sleep(5 * time.Second) 43 | fmt.Println(tairClient.ExHGetAll(ctx, key)) 44 | } 45 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient 14 | 15 | func init() { 16 | tairClient = tair.NewTairClient(&redis.Options{ 17 | Addr: "xxx.redis.rds.aliyuncs.com:6379", 18 | Password: "xxx", 19 | DB: 0, 20 | }) 21 | } 22 | 23 | func main() { 24 | err := tairClient.ExSet(ctx, "exkey", "exval").Err() 25 | if err != nil { 26 | fmt.Println(err.Error()) 27 | } 28 | 29 | val, err := tairClient.ExGet(ctx, "exkey").Result() 30 | if err != nil { 31 | panic(err) 32 | } 33 | fmt.Println("get exkey values is: ", val) 34 | } 35 | -------------------------------------------------------------------------------- /example/taircluster/example_taircluster.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | 8 | "github.com/alibaba/tair-go/tair" 9 | "github.com/redis/go-redis/v9" 10 | ) 11 | 12 | var ctx = context.Background() 13 | 14 | var clusterClient *tair.TairClusterClient 15 | 16 | var ip = "127.0.0.1" 17 | 18 | func init() { 19 | clusterClient = tair.NewTairClusterClient(&tair.TairClusterOptions{ 20 | ClusterOptions: &redis.ClusterOptions{ 21 | Addrs: []string{ 22 | "", ip + ":30001", ip + ":30002", 23 | ip + ":30003", ip + ":30004", ip + ":30005", ip + ":30006", 24 | }, 25 | }, 26 | }) 27 | } 28 | 29 | // ExampleTairClusterClient test tair module command with TairClusterClient 30 | func ExampleTairClusterClient() { 31 | setRes, err := clusterClient.ExSet(ctx, "key1", "value1").Result() 32 | if err != nil { 33 | fmt.Println("ExSet occurs err:", err) 34 | } 35 | fmt.Printf("ExSeT result: %v\n", setRes) 36 | 37 | getRes, err := clusterClient.ExGet(ctx, "key1").Result() 38 | if err != nil { 39 | fmt.Println("ExGet occurs err: ", err) 40 | } 41 | if ok := reflect.DeepEqual("value1", getRes[0]); !ok { 42 | fmt.Println("ExGet occurs err: ", err) 43 | } else { 44 | fmt.Printf("ExGeT result: %v\n", getRes) 45 | } 46 | } 47 | 48 | func main() { 49 | ExampleTairClusterClient() 50 | } 51 | -------------------------------------------------------------------------------- /example/tairhash/example_hash.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | var ctx = context.Background() 12 | 13 | var tairClient *tair.TairClient // 全局客户端 14 | 15 | var ip = "127.0.0.1" 16 | 17 | func init() { 18 | tairClient = tair.NewTairClient(&redis.Options{ 19 | Addr: ip + ":" + "6379", 20 | Password: "", // no password set 21 | DB: 0, // use default DB 22 | }) 23 | } 24 | 25 | func ExampleTairHash() { 26 | err := tairClient.ExHSet(ctx, "h-k-1", "f-1", "v-1").Err() 27 | if err != nil { 28 | fmt.Println(err.Error()) 29 | panic(err) 30 | } 31 | err = tairClient.ExHSet(ctx, "h-k-1", "f-2", "v-2").Err() 32 | if err != nil { 33 | fmt.Println(err.Error()) 34 | } 35 | val, err := tairClient.ExHGet(ctx, "h-k-1", "f-1").Result() 36 | if err != nil { 37 | fmt.Println(err.Error()) 38 | panic(err) 39 | } 40 | fmt.Println("key", val) 41 | val, err = tairClient.ExHGet(ctx, "h-k-1", "f-2").Result() 42 | if err != nil { 43 | fmt.Println(err.Error()) 44 | panic(err) 45 | } 46 | fmt.Println("key", val) 47 | } 48 | 49 | func main() { 50 | ExampleTairHash() 51 | } 52 | -------------------------------------------------------------------------------- /example/tairstring/distribute_lock.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | "sync" 8 | "time" 9 | 10 | "github.com/alibaba/tair-go/tair" 11 | "github.com/redis/go-redis/v9" 12 | ) 13 | 14 | var ctx = context.Background() 15 | 16 | var tairClient *tair.TairClient 17 | 18 | var lockKey = "LOCK_KEY" 19 | 20 | func init() { 21 | tairClient = tair.NewTairClient(&redis.Options{ 22 | Addr: "127.0.0.1:6379", 23 | Password: "", 24 | DB: 0, 25 | }) 26 | } 27 | 28 | // TryLock locks atomically via setnx 29 | // requestId prevents the lock from being deleted by mistake 30 | // expireTime is to prevent the deadlock of business machine downtime 31 | func TryLock(key, requestId string, expireTime time.Duration) bool { 32 | res, err := tairClient.SetNX(ctx, key, requestId, expireTime).Result() 33 | if err != nil { 34 | fmt.Println(err.Error()) 35 | return false 36 | } 37 | return res 38 | } 39 | 40 | // ReleaseLock atomically releases the lock via the CAD command 41 | // requestId ensures that the released lock is added by itself 42 | func ReleaseLock(key, requestId string) bool { 43 | res, err := tairClient.Cad(ctx, key, requestId).Result() 44 | if err != nil { 45 | fmt.Println(err.Error()) 46 | return false 47 | } 48 | return res == int64(1) 49 | } 50 | 51 | type Account struct { 52 | Balance int 53 | } 54 | 55 | func randomString(length int) string { 56 | rand.Seed(time.Now().UnixNano()) 57 | b := make([]byte, length) 58 | rand.Read(b) 59 | return fmt.Sprintf("%x", b)[:length] 60 | } 61 | 62 | func depositAndWithdraw(account *Account) { 63 | var requestId = randomString(10) 64 | if TryLock(lockKey, requestId, 2*time.Second) { 65 | fmt.Println("Balance:", account.Balance) 66 | if account.Balance != 10 { 67 | panic(fmt.Sprintf("Balance should not be negative value: %d", account.Balance)) 68 | } 69 | account.Balance += 1000 70 | time.Sleep(time.Second) 71 | account.Balance -= 1000 72 | ReleaseLock(lockKey, requestId) 73 | } 74 | time.Sleep(time.Second) 75 | } 76 | 77 | func main() { 78 | var wg sync.WaitGroup 79 | 80 | account := &Account{Balance: 10} 81 | wg.Add(10) 82 | for i := 0; i < 10; i++ { 83 | go func() { 84 | for { 85 | depositAndWithdraw(account) 86 | } 87 | }() 88 | } 89 | wg.Wait() 90 | } 91 | -------------------------------------------------------------------------------- /example/tairvector/example_vector.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | "math/rand" 10 | ) 11 | 12 | var ctx = context.Background() 13 | 14 | var tairClient *tair.TairClient // 全局客户端 15 | 16 | var ip = "127.0.0.1" 17 | 18 | func init() { 19 | tairClient = tair.NewTairClient(&redis.Options{ 20 | Addr: ip + ":" + "6379", 21 | Password: "", // no password set 22 | DB: 0, // use default DB 23 | }) 24 | } 25 | 26 | func ExampleTairVector() { 27 | index := "vector-index" 28 | dim := 4 29 | tairClient.TvsCreateIndex(ctx, index, dim, "HNSW", "L2", 30 | tair.TvsCreateIndexArgs{}.New().AutoGc(true).M(16).DataType("FLOAT16")).Result() 31 | 32 | for i := 0; i < 100; i++ { 33 | key := fmt.Sprintf("test_key_%d", i) 34 | fields := make(map[string]interface{}) 35 | fields["field1"] = "value1" 36 | fields["field2"] = rand.Intn(100) 37 | fields["field3"] = rand.Float32() 38 | fields["field4"] = rand.Intn(2) == 0 39 | floats := make([]float32, dim) 40 | for i := 0; i < dim; i++ { 41 | floats[i] = rand.Float32() 42 | } 43 | b, _ := json.Marshal(floats) 44 | fields["VECTOR"] = string(b) 45 | 46 | result, err := tairClient.TvsHSet(ctx, index, key, tair.TvsHSetArgs{}.New().Fields(fields)).Result() 47 | if err != nil { 48 | fmt.Println(err.Error()) 49 | panic(err) 50 | } 51 | 52 | if result != 5 { 53 | fmt.Println("tvsHSet failed") 54 | panic(result) 55 | } 56 | } 57 | 58 | result, err := tairClient.TvsKnnSearch(ctx, index, 10, "[0,0,0,0]", nil).Result() 59 | if err != nil { 60 | fmt.Println(err.Error()) 61 | panic(err) 62 | } 63 | fmt.Println(result) 64 | 65 | tairClient.TvsDelIndex(ctx, index) 66 | } 67 | 68 | func main() { 69 | ExampleTairVector() 70 | } 71 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/tair-go 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/onsi/ginkgo v1.16.5 7 | github.com/onsi/gomega v1.18.1 8 | github.com/redis/go-redis/v9 v9.5.3 9 | github.com/stretchr/testify v1.9.0 10 | ) 11 | 12 | require ( 13 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | gopkg.in/yaml.v3 v3.0.1 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 2 | github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 3 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 4 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 6 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 7 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 12 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 13 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 14 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 15 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 16 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 17 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 18 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 19 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 20 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 21 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 22 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 23 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 24 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 25 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 26 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 27 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 28 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 30 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 31 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 32 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 33 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 34 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 35 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 36 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 37 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 38 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 39 | github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= 40 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 41 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 42 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 43 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= 44 | github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= 45 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 46 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 47 | github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU= 48 | github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= 49 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 50 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 51 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 52 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 53 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 54 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 55 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 56 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 57 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 58 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 59 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 60 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 61 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 62 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 63 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 64 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 65 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 66 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 67 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 68 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 69 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 70 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 71 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 72 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 73 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 74 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 75 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 76 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 77 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 78 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 79 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 80 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 81 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 82 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 83 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 84 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 85 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 86 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 87 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 88 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 89 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 90 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 91 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 92 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 93 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 94 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 95 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 96 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 97 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 98 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 99 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 100 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 101 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 102 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 103 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 104 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 105 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 106 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 107 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 108 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 109 | -------------------------------------------------------------------------------- /tair/arg.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | type arger interface { 4 | GetArgs() []interface{} 5 | } 6 | 7 | type arg struct { 8 | arger 9 | Set map[string]bool 10 | } 11 | 12 | const ( 13 | XX = "xx" 14 | NX = "nx" 15 | 16 | PX = "px" 17 | EX = "ex" 18 | EXAT = "exat" 19 | PXAT = "pxat" 20 | 21 | VER = "ver" 22 | ABS = "abs" 23 | 24 | MAX = "MAX" 25 | MIN = "MIN" 26 | 27 | DEF = "def" 28 | NONEGATIVE = "nonegative" 29 | 30 | FLAGS = "flags" 31 | KEEPTTL = "keepttl" 32 | 33 | CH = "ch" 34 | INCR = "incr" 35 | WITHSCORES = "withscores" 36 | LIMIT = "limit" 37 | GT = "gt" 38 | 39 | CAPACITY = "CAPACITY" 40 | ERROR = "ERROR" 41 | NOCREATE = "NOCREATE" 42 | ITEMS = "ITEMS" 43 | 44 | UNCOMPRESSED = "UNCOMPRESSED" 45 | DATA_ET = "DATA_ET" 46 | CHUNK_SIZE = "CHUNK_SIZE" 47 | LABELS = "LABELS" 48 | 49 | MAXCOUNT = "MAXCOUNT" 50 | WITHLABELS = "WITHLABELS" 51 | REVERSE = "REVERSE" 52 | FILTER = "FILTER" 53 | AGGREGATION = "AGGREGATION" 54 | SUM = "SUM" 55 | AVG = "AVG" 56 | STDP = "STD.P" 57 | STDS = "STD.S" 58 | COUNT = "COUNT" 59 | FIRST = "FIRST" 60 | LAST = "LAST" 61 | RANGE = "RANGE" 62 | 63 | FORMAT = "format" 64 | ROOTNAME = "rootname" 65 | ARRNAME = "arrname" 66 | 67 | SIZE = "size" 68 | WIN = "win" 69 | 70 | RADIUS = "radius" 71 | MEMBER = "member" 72 | 73 | WITHOUTWKT = "withoutwkt" 74 | WITHVALUE = "withvalue" 75 | WITHOUTVALUE = "withoutvalue" 76 | WITHDIST = "withdist" 77 | ASC = "asc" 78 | DESC = "desc" 79 | ) 80 | 81 | const ( 82 | ValueIsNull = "ERR:The value is null" 83 | ValueIsEmpty = "ERR:The value is empty" 84 | KeyIsNull = "ERR:The key is null" 85 | KeyIsEmpty = "ERR:The key is empty" 86 | MultiExpireParam = "ERR:The expire param is not single" 87 | ExpIsSet = "ERR:The expire param has been set" 88 | OptionIllegal = "ERR:The option argument error" 89 | ) 90 | -------------------------------------------------------------------------------- /tair/main_test.go: -------------------------------------------------------------------------------- 1 | package tair_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/alibaba/tair-go/tair" 9 | "github.com/redis/go-redis/v9" 10 | ) 11 | 12 | const ( 13 | redisIP = "127.0.0.1" 14 | redisPort = "6379" 15 | redisAddr = redisIP + ":" + redisPort 16 | redisSecondaryPort = "6381" 17 | ) 18 | 19 | const ( 20 | ringShard1Port = "6390" 21 | ringShard2Port = "6391" 22 | ringShard3Port = "6392" 23 | ) 24 | 25 | const ( 26 | sentinelName = "mymaster" 27 | sentinelMasterPort = "9123" 28 | sentinelSlave1Port = "9124" 29 | sentinelSlave2Port = "9125" 30 | sentinelPort1 = "9126" 31 | sentinelPort2 = "9127" 32 | sentinelPort3 = "9128" 33 | ) 34 | 35 | var ( 36 | sentinelAddrs = []string{":" + sentinelPort1, ":" + sentinelPort2, ":" + sentinelPort3} 37 | 38 | processes map[string]*redisProcess 39 | 40 | redisMain *redisProcess 41 | ringShard1, ringShard2, ringShard3 *redisProcess 42 | sentinelMaster, sentinelSlave1, sentinelSlave2 *redisProcess 43 | sentinel1, sentinel2, sentinel3 *redisProcess 44 | ) 45 | 46 | func registerProcess(port string, p *redisProcess) { 47 | if processes == nil { 48 | processes = make(map[string]*redisProcess) 49 | } 50 | processes[port] = p 51 | } 52 | 53 | type redisProcess struct { 54 | *os.Process 55 | *redis.Client 56 | } 57 | 58 | func redisOptions() *redis.Options { 59 | return &redis.Options{ 60 | Addr: redisAddr, 61 | DB: 15, 62 | DialTimeout: 10 * time.Second, 63 | ReadTimeout: 30 * time.Second, 64 | WriteTimeout: 30 * time.Second, 65 | MaxRetries: -1, 66 | PoolSize: 10, 67 | PoolTimeout: 30 * time.Second, 68 | ConnMaxIdleTime: time.Minute, 69 | Protocol: 2, 70 | } 71 | } 72 | 73 | func redisClusterOptions() *redis.ClusterOptions { 74 | return &redis.ClusterOptions{ 75 | DialTimeout: 10 * time.Second, 76 | ReadTimeout: 30 * time.Second, 77 | WriteTimeout: 30 * time.Second, 78 | 79 | MaxRedirects: 8, 80 | 81 | PoolSize: 10, 82 | PoolTimeout: 30 * time.Second, 83 | ConnMaxIdleTime: time.Minute, 84 | Protocol: 2, 85 | } 86 | } 87 | 88 | var cluster = &clusterScenario{ 89 | ports: []string{"30001", "30002", "30003", "30004", "30005", "30006"}, 90 | nodeIDs: make([]string, 6), 91 | processes: make(map[string]*redisProcess, 6), 92 | clients: make(map[string]*tair.TairClient, 6), 93 | } 94 | 95 | func eventually(fn func() error, timeout time.Duration) error { 96 | errCh := make(chan error, 1) 97 | done := make(chan struct{}) 98 | exit := make(chan struct{}) 99 | 100 | go func() { 101 | for { 102 | err := fn() 103 | if err == nil { 104 | close(done) 105 | return 106 | } 107 | 108 | select { 109 | case errCh <- err: 110 | default: 111 | } 112 | 113 | select { 114 | case <-exit: 115 | return 116 | case <-time.After(timeout / 100): 117 | } 118 | } 119 | }() 120 | 121 | select { 122 | case <-done: 123 | return nil 124 | case <-time.After(timeout): 125 | close(exit) 126 | select { 127 | case err := <-errCh: 128 | return err 129 | default: 130 | return fmt.Errorf("timeout after %s without an error", timeout) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tair/tair.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/redis/go-redis/v9" 7 | ) 8 | 9 | var _ TairCmdable = (*TairClient)(nil) 10 | 11 | type TairClient struct { 12 | *redis.Client 13 | tairCmdable 14 | ctx context.Context 15 | } 16 | 17 | func NewTairClient(opt *redis.Options) *TairClient { 18 | opt.Protocol = 2 // For tair, only resp2 is used 19 | c := TairClient{Client: redis.NewClient(opt)} 20 | c.tairCmdable = c.Process 21 | return &c 22 | } 23 | 24 | func (t *TairClient) TairPipeline() TairPipeline { 25 | 26 | pipe := TairPipeline{ 27 | Pipeline: t.Client.Pipeline().(*redis.Pipeline), 28 | } 29 | pipe.init() 30 | return pipe 31 | } 32 | 33 | func (t *TairClient) TairPipelined(ctx context.Context, fn func(redis.Pipeliner) error) ([]redis.Cmder, error) { 34 | return t.Client.Pipeline().Pipelined(ctx, fn) 35 | } 36 | -------------------------------------------------------------------------------- /tair/tairbloom.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/redis/go-redis/v9" 7 | ) 8 | 9 | type BfInsertArgs struct { 10 | arg 11 | capacity int64 12 | errorRate float64 13 | } 14 | 15 | func (a *BfInsertArgs) JoinArgs(key string, items ...string) []interface{} { 16 | args := make([]interface{}, 0) 17 | args = append(args, key) 18 | if _, ok := a.Set[NOCREATE]; ok { 19 | args = append(args, NOCREATE) 20 | } 21 | args = append(args, CAPACITY, a.capacity, ERROR, a.errorRate) 22 | args = append(args, ITEMS) 23 | for _, item := range items { 24 | args = append(args, item) 25 | } 26 | return args 27 | } 28 | 29 | func (a BfInsertArgs) New() *BfInsertArgs { 30 | a.Set = make(map[string]bool) 31 | return &a 32 | } 33 | 34 | func (a *BfInsertArgs) Capacity(initCapacity int64) *BfInsertArgs { 35 | a.Set[CAPACITY] = true 36 | a.capacity = initCapacity 37 | return a 38 | } 39 | 40 | func (a *BfInsertArgs) ErrorRate(errorRate float64) *BfInsertArgs { 41 | a.Set[ERROR] = true 42 | a.errorRate = errorRate 43 | return a 44 | } 45 | 46 | func (a *BfInsertArgs) NoCreate() *BfInsertArgs { 47 | a.Set[NOCREATE] = true 48 | return a 49 | } 50 | 51 | type BfMExistArgs struct { 52 | arg 53 | } 54 | 55 | func (a *BfMExistArgs) JoinArgs(key string, items ...string) []interface{} { 56 | args := make([]interface{}, 0) 57 | args = append(args, key) 58 | for _, item := range items { 59 | args = append(args, item) 60 | } 61 | return args 62 | } 63 | 64 | func (a BfMExistArgs) New() *BfMExistArgs { 65 | a.Set = make(map[string]bool) 66 | return &a 67 | } 68 | 69 | type BfMAddArgs struct { 70 | arg 71 | } 72 | 73 | func (a *BfMAddArgs) JoinArgs(key string, items ...string) []interface{} { 74 | args := make([]interface{}, 0) 75 | args = append(args, key) 76 | for _, item := range items { 77 | args = append(args, item) 78 | } 79 | return args 80 | } 81 | 82 | func (a BfMAddArgs) New() *BfMAddArgs { 83 | a.Set = make(map[string]bool) 84 | return &a 85 | } 86 | 87 | func (tc tairCmdable) BfReserve(ctx context.Context, key string, initCapacity int64, errorRate float64) *redis.StringCmd { 88 | args := make([]interface{}, 4) 89 | args[0] = "BF.RESERVE" 90 | args[1] = key 91 | args[2] = errorRate 92 | args[3] = initCapacity 93 | cmd := redis.NewStringCmd(ctx, args...) 94 | _ = tc(ctx, cmd) 95 | return cmd 96 | } 97 | 98 | func (tc tairCmdable) BfAdd(ctx context.Context, key string, item string) *redis.BoolCmd { 99 | args := make([]interface{}, 3) 100 | args[0] = "BF.ADD" 101 | args[1] = key 102 | args[2] = item 103 | cmd := redis.NewBoolCmd(ctx, args...) 104 | _ = tc(ctx, cmd) 105 | return cmd 106 | } 107 | 108 | func (tc tairCmdable) BfMAdd(ctx context.Context, key string, items ...string) *redis.BoolSliceCmd { 109 | args := make([]interface{}, 1) 110 | args[0] = "BF.MADD" 111 | a := BfMAddArgs{}.New().JoinArgs(key, items...) 112 | args = append(args, a...) 113 | cmd := redis.NewBoolSliceCmd(ctx, args...) 114 | _ = tc(ctx, cmd) 115 | return cmd 116 | } 117 | 118 | func (tc tairCmdable) BfExists(ctx context.Context, key string, item string) *redis.BoolCmd { 119 | args := make([]interface{}, 3) 120 | args[0] = "BF.EXISTS" 121 | args[1] = key 122 | args[2] = item 123 | cmd := redis.NewBoolCmd(ctx, args...) 124 | _ = tc(ctx, cmd) 125 | return cmd 126 | } 127 | 128 | func (tc tairCmdable) BfMExists(ctx context.Context, key string, items ...string) *redis.BoolSliceCmd { 129 | args := make([]interface{}, 1) 130 | args[0] = "BF.MEXISTS" 131 | a := BfMExistArgs{}.New().JoinArgs(key, items...) 132 | args = append(args, a...) 133 | cmd := redis.NewBoolSliceCmd(ctx, args...) 134 | _ = tc(ctx, cmd) 135 | return cmd 136 | } 137 | 138 | func (tc tairCmdable) BfInsert(ctx context.Context, key string, bfInsertArgs *BfInsertArgs, items ...string) *redis.BoolSliceCmd { 139 | args := make([]interface{}, 1) 140 | args[0] = "BF.INSERT" 141 | args = append(args, bfInsertArgs.JoinArgs(key, items...)...) 142 | cmd := redis.NewBoolSliceCmd(ctx, args...) 143 | _ = tc(ctx, cmd) 144 | return cmd 145 | } 146 | 147 | func (tc tairCmdable) BfDebug(ctx context.Context, key string) *redis.StringSliceCmd { 148 | args := make([]interface{}, 2) 149 | args[0] = "BF.DEBUG" 150 | args[1] = key 151 | cmd := redis.NewStringSliceCmd(ctx, args...) 152 | _ = tc(ctx, cmd) 153 | return cmd 154 | } 155 | -------------------------------------------------------------------------------- /tair/tairbloom_test.go: -------------------------------------------------------------------------------- 1 | package tair_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | 8 | "github.com/alibaba/tair-go/tair" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/suite" 11 | ) 12 | 13 | type TairBloomTestSuite struct { 14 | suite.Suite 15 | tairClient *tair.TairClient 16 | } 17 | 18 | var randomkey_ string = "randomkey_" + randStr(20) 19 | 20 | // var randomKeyBinary_ []byte 21 | 22 | var bbf string = "bbf" + randStr(20) 23 | 24 | // var bcf []byte 25 | 26 | func randStr(size int) string { 27 | str := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 28 | bytes := []byte(str) 29 | var result []byte 30 | rand.Seed(time.Now().UnixNano() + int64(rand.Intn(100000))) 31 | for i := 0; i < size; i++ { 32 | result = append(result, bytes[rand.Intn(len(bytes))]) 33 | } 34 | return string(result) 35 | } 36 | 37 | func (suite *TairBloomTestSuite) SetupTest() { 38 | suite.tairClient = tair.NewTairClient(redisOptions()) 39 | assert.Equal(suite.T(), "OK", suite.tairClient.FlushDB(ctx).Val()) 40 | } 41 | 42 | func (suite *TairBloomTestSuite) TearDownTest() { 43 | assert.NoError(suite.T(), suite.tairClient.Close()) 44 | } 45 | 46 | func (suite *TairBloomTestSuite) BeforeTest(suiteName, testName string) { 47 | //fmt.Printf("BeforeTest: suiteName=%s,testName=%s\n", suiteName, testName) 48 | //fmt.Println("dddddddd") 49 | //randomkey_ = "randomkey_" + randStr(20) 50 | ////randomKeyBinary_ = []byte("randomKeyBinary_" + randStr(20)) 51 | //bbf = "bbf" + randStr(20) 52 | //bcf = []byte("bcf" + randStr(20)) 53 | } 54 | 55 | func (suite *TairBloomTestSuite) TestBfAdd() { 56 | r1, err1 := suite.tairClient.BfReserve(ctx, bbf, 100, 0.001).Result() 57 | assert.NoError(suite.T(), err1) 58 | assert.Equal(suite.T(), r1, "OK") 59 | 60 | r2, err2 := suite.tairClient.BfAdd(ctx, bbf, "val1").Result() 61 | assert.NoError(suite.T(), err2) 62 | assert.Equal(suite.T(), r2, true) 63 | 64 | r3, err3 := suite.tairClient.BfExists(ctx, bbf, "val1").Result() 65 | assert.NoError(suite.T(), err3) 66 | assert.Equal(suite.T(), r3, true) 67 | 68 | r4, err4 := suite.tairClient.BfExists(ctx, bbf, "val2").Result() 69 | assert.NoError(suite.T(), err4) 70 | assert.Equal(suite.T(), r4, false) 71 | } 72 | 73 | func (suite *TairBloomTestSuite) TestBfMAdd() { 74 | r1, err1 := suite.tairClient.BfReserve(ctx, bbf, 100, 0.001).Result() 75 | assert.NoError(suite.T(), err1) 76 | assert.Equal(suite.T(), r1, "OK") 77 | 78 | r2, err2 := suite.tairClient.BfMAdd(ctx, bbf, "val1", "val2").Result() 79 | assert.NoError(suite.T(), err2) 80 | assert.Equal(suite.T(), r2[0], true) 81 | assert.Equal(suite.T(), r2[1], true) 82 | } 83 | 84 | func (suite *TairBloomTestSuite) TestBfInsert() { 85 | r1, err1 := suite.tairClient.BfReserve(ctx, bbf, 100, 0.001).Result() 86 | assert.NoError(suite.T(), err1) 87 | assert.Equal(suite.T(), r1, "OK") 88 | a := tair.BfInsertArgs{}.New().Capacity(100).ErrorRate(0.001) 89 | r4, err4 := suite.tairClient.BfInsert(ctx, bbf, a, "val1", "val2").Result() 90 | assert.NoError(suite.T(), err4) 91 | assert.Equal(suite.T(), r4[0], true) 92 | assert.Equal(suite.T(), r4[1], true) 93 | 94 | r2, err2 := suite.tairClient.BfMAdd(ctx, bbf, "val3", "val4").Result() 95 | assert.NoError(suite.T(), err2) 96 | assert.Equal(suite.T(), r2[0], true) 97 | assert.Equal(suite.T(), r2[1], true) 98 | 99 | r3, err3 := suite.tairClient.BfMExists(ctx, bbf, "val3", "val4").Result() 100 | assert.NoError(suite.T(), err3) 101 | assert.Equal(suite.T(), r3[0], true) 102 | assert.Equal(suite.T(), r3[1], true) 103 | a1 := tair.BfInsertArgs{}.New().Capacity(100).ErrorRate(0.001) 104 | 105 | b, err := suite.tairClient.BfInsert(ctx, randomkey_, a1, "item1", "item2", "item3", "item4", "item5").Result() 106 | assert.NoError(suite.T(), err) 107 | for _, r := range b { 108 | assert.Equal(suite.T(), r, true) 109 | } 110 | 111 | b1, err1 := suite.tairClient.BfMExists(ctx, randomkey_, "item1", "item2", "item3", "item4", "item5").Result() 112 | assert.NoError(suite.T(), err1) 113 | for _, r := range b1 { 114 | assert.Equal(suite.T(), r, true) 115 | } 116 | } 117 | 118 | func (suite *TairBloomTestSuite) TestBfCommand() { 119 | r1, err1 := suite.tairClient.BfReserve(ctx, bbf, 100, 0.001).Result() 120 | assert.NoError(suite.T(), err1) 121 | assert.Equal(suite.T(), r1, "OK") 122 | 123 | b, err := suite.tairClient.BfAdd(ctx, randomkey_, "item1").Result() 124 | assert.NoError(suite.T(), err) 125 | assert.Equal(suite.T(), b, true) 126 | 127 | b2, err2 := suite.tairClient.BfExists(ctx, randomkey_, "item1").Result() 128 | assert.NoError(suite.T(), err2) 129 | assert.Equal(suite.T(), b2, true) 130 | 131 | b3, err3 := suite.tairClient.BfExists(ctx, randomkey_, "item2").Result() 132 | assert.NoError(suite.T(), err3) 133 | assert.Equal(suite.T(), b3, false) 134 | } 135 | 136 | func (suite *TairBloomTestSuite) TestBfAddException() { 137 | suite.tairClient.BfAdd(ctx, randomkey_, randomkey_) 138 | suite.tairClient.Set(ctx, randomkey_, "bar", 0) 139 | res1, err1 := suite.tairClient.BfAdd(ctx, randomkey_, randomkey_).Result() 140 | assert.Error(suite.T(), err1) 141 | assert.Contains(suite.T(), err1, "WRONGTYPE") 142 | assert.Equal(suite.T(), res1, false) 143 | } 144 | 145 | func (suite *TairBloomTestSuite) TestBfMAddException() { 146 | suite.tairClient.BfMAdd(ctx, randomkey_, "item") 147 | suite.tairClient.Set(ctx, randomkey_, "bar", 0) 148 | res1, err1 := suite.tairClient.BfMAdd(ctx, randomkey_, "item").Result() 149 | assert.Error(suite.T(), err1) 150 | assert.Contains(suite.T(), err1, "WRONGTYPE") 151 | for _, r := range res1 { 152 | assert.Equal(suite.T(), r, false) 153 | } 154 | } 155 | 156 | func (suite *TairBloomTestSuite) TestBfExistException() { 157 | suite.tairClient.BfExists(ctx, randomkey_, "item") 158 | suite.tairClient.Set(ctx, randomkey_, "bar", 0) 159 | res1, err1 := suite.tairClient.BfExists(ctx, randomkey_, "item").Result() 160 | assert.Error(suite.T(), err1) 161 | assert.Contains(suite.T(), err1, "WRONGTYPE") 162 | assert.Equal(suite.T(), res1, false) 163 | } 164 | 165 | func (suite *TairBloomTestSuite) TestBfMExistException() { 166 | suite.tairClient.BfMExists(ctx, randomkey_, "item") 167 | suite.tairClient.Set(ctx, randomkey_, "bar", 0) 168 | res1, err1 := suite.tairClient.BfMExists(ctx, randomkey_, "item").Result() 169 | assert.Error(suite.T(), err1) 170 | assert.Contains(suite.T(), err1, "WRONGTYPE") 171 | for _, r := range res1 { 172 | assert.Equal(suite.T(), r, false) 173 | } 174 | } 175 | 176 | func (suite *TairBloomTestSuite) TestBfInsertException() { 177 | a := tair.BfInsertArgs{}.New().Capacity(100).ErrorRate(0.001) 178 | suite.tairClient.BfInsert(ctx, randomkey_, a, "item") 179 | suite.tairClient.Set(ctx, randomkey_, "bar", 0) 180 | _, err1 := suite.tairClient.BfInsert(ctx, randomkey_, a, "item").Result() 181 | assert.Error(suite.T(), err1) 182 | assert.Contains(suite.T(), err1, "WRONGTYPE") 183 | } 184 | 185 | func (suite *TairBloomTestSuite) TestBfReserveException() { 186 | suite.tairClient.BfReserve(ctx, randomkey_, 1, 0.01) 187 | suite.tairClient.Set(ctx, randomkey_, "bar", 0) 188 | res1, err1 := suite.tairClient.BfReserve(ctx, randomkey_, 1, 0.01).Result() 189 | assert.Error(suite.T(), err1) 190 | assert.Contains(suite.T(), err1, "WRONGTYPE") 191 | for _, r := range res1 { 192 | assert.Equal(suite.T(), r, false) 193 | } 194 | } 195 | 196 | func (suite *TairBloomTestSuite) TestBfDebugException() { 197 | suite.tairClient.BfDebug(ctx, randomkey_) 198 | suite.tairClient.Set(ctx, randomkey_, "bar", 0) 199 | _, err1 := suite.tairClient.BfDebug(ctx, randomkey_).Result() 200 | assert.Error(suite.T(), err1) 201 | assert.Contains(suite.T(), err1, "WRONGTYPE") 202 | } 203 | 204 | func TestTairBloomTestSuite(t *testing.T) { 205 | suite.Run(t, new(TairBloomTestSuite)) 206 | } 207 | -------------------------------------------------------------------------------- /tair/taircluster.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/redis/go-redis/v9" 7 | ) 8 | 9 | var _ TairCmdable = (*TairClusterClient)(nil) 10 | 11 | type TairClusterClient struct { 12 | *redis.ClusterClient 13 | tairCmdable 14 | ctx context.Context 15 | } 16 | 17 | type TairClusterOptions struct { 18 | *redis.ClusterOptions 19 | } 20 | 21 | func (opt *TairClusterOptions) init() { 22 | } 23 | 24 | func NewTairClusterClient(opt *TairClusterOptions) *TairClusterClient { 25 | opt.init() 26 | tc := &TairClusterClient{ 27 | ClusterClient: redis.NewClusterClient(opt.ClusterOptions), 28 | ctx: context.Background(), 29 | } 30 | tc.tairCmdable = tc.Process 31 | return tc 32 | } 33 | 34 | func (t *TairClusterClient) TairPipeline() TairPipeline { 35 | pipe := TairPipeline{ 36 | Pipeline: t.ClusterClient.Pipeline().(*redis.Pipeline), 37 | } 38 | pipe.init() 39 | return pipe 40 | } 41 | 42 | func (t *TairClusterClient) TairPipelined(ctx context.Context, fn func(redis.Pipeliner) error) ([]redis.Cmder, error) { 43 | return t.ClusterClient.Pipeline().Pipelined(ctx, fn) 44 | } 45 | -------------------------------------------------------------------------------- /tair/taircluster_test.go: -------------------------------------------------------------------------------- 1 | package tair_test 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/suite" 11 | 12 | "github.com/alibaba/tair-go/tair" 13 | "github.com/redis/go-redis/v9" 14 | ) 15 | 16 | var ( 17 | ctx = context.Background() 18 | testHost = "127.0.0.1" 19 | ) 20 | 21 | type clusterScenario struct { 22 | ports []string 23 | nodeIDs []string 24 | processes map[string]*redisProcess 25 | clients map[string]*tair.TairClient 26 | } 27 | 28 | func (s *clusterScenario) addrs() []string { 29 | addrs := make([]string, len(s.ports)) 30 | for i, port := range s.ports { 31 | addrs[i] = net.JoinHostPort(testHost, port) 32 | } 33 | return addrs 34 | } 35 | 36 | func (s *clusterScenario) newClusterClientUnstable(opt *redis.ClusterOptions) *tair.TairClusterClient { 37 | opt.Addrs = s.addrs() 38 | options := &tair.TairClusterOptions{ClusterOptions: opt} 39 | return tair.NewTairClusterClient(options) 40 | } 41 | 42 | func (s *clusterScenario) newClusterClient( 43 | ctx context.Context, opt *redis.ClusterOptions, 44 | ) *tair.TairClusterClient { 45 | client := s.newClusterClientUnstable(opt) 46 | 47 | err := eventually(func() error { 48 | if opt.ClusterSlots != nil { 49 | return nil 50 | } 51 | return nil 52 | }, 30*time.Second) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | return client 58 | } 59 | 60 | type TairClusterTestSuite struct { 61 | suite.Suite 62 | tairClient *tair.TairClusterClient 63 | } 64 | 65 | func (suite *TairClusterTestSuite) SetupTest() { 66 | suite.tairClient = cluster.newClusterClient(ctx, redisClusterOptions()) 67 | err := suite.tairClient.ForEachMaster(ctx, func(ctx context.Context, master *redis.Client) error { 68 | return master.FlushDB(ctx).Err() 69 | }) 70 | assert.NoError(suite.T(), err) 71 | } 72 | 73 | func (suite *TairClusterTestSuite) TearDownTest() { 74 | _ = suite.tairClient.ForEachMaster(ctx, func(ctx context.Context, master *redis.Client) error { 75 | return master.FlushDB(ctx).Err() 76 | }) 77 | assert.NoError(suite.T(), suite.tairClient.Close()) 78 | } 79 | 80 | func (suite *TairClusterTestSuite) TestClusterCas() { 81 | suite.tairClient.Set(ctx, "k1", "v1", 0) 82 | n, err := suite.tairClient.Cas(ctx, "k1", "v2", "v3").Result() 83 | assert.NoError(suite.T(), err) 84 | assert.Equal(suite.T(), n, int64(0)) 85 | 86 | n, err = suite.tairClient.Cas(ctx, "k1", "v1", "v3").Result() 87 | assert.NoError(suite.T(), err) 88 | assert.Equal(suite.T(), n, int64(1)) 89 | 90 | res, err := suite.tairClient.Get(ctx, "k1").Result() 91 | assert.NoError(suite.T(), err) 92 | assert.Equal(suite.T(), res, "v3") 93 | } 94 | 95 | func (suite *TairClusterTestSuite) TestClusterCasArgs() { 96 | suite.tairClient.Set(ctx, "foo", "bzz", 0) 97 | suite.tairClient.CasArgs(ctx, "foo", "bzz", "too", tair.CasArgs{}.New().Ex(1)) 98 | 99 | result, err := suite.tairClient.Get(ctx, "foo").Result() 100 | assert.Equal(suite.T(), result, "too") 101 | assert.NoError(suite.T(), err) 102 | time.Sleep(time.Duration(2) * time.Second) 103 | 104 | result1, err1 := suite.tairClient.Get(ctx, "foo").Result() 105 | assert.Error(suite.T(), err1) 106 | assert.Equal(suite.T(), result1, "") 107 | } 108 | 109 | func (suite *TairClusterTestSuite) TestClusterExHSet() { 110 | res, err := suite.tairClient.ExHSet(ctx, "k1", "f1", "v1").Result() 111 | assert.NoError(suite.T(), err) 112 | assert.Equal(suite.T(), res, int64(1)) 113 | 114 | res, err = suite.tairClient.Exists(ctx, "k1").Result() 115 | assert.NoError(suite.T(), err) 116 | assert.Equal(suite.T(), res, int64(1)) 117 | 118 | result, err := suite.tairClient.ExHGet(ctx, "k1", "f1").Result() 119 | assert.NoError(suite.T(), err) 120 | assert.Equal(suite.T(), result, "v1") 121 | } 122 | 123 | func (suite *TairClusterTestSuite) TestClusterExHset() { 124 | res, err := suite.tairClient.ExHSet(ctx, "k1", "f1", "v1").Result() 125 | assert.NoError(suite.T(), err) 126 | assert.Equal(suite.T(), res, int64(1)) 127 | 128 | res, err = suite.tairClient.Exists(ctx, "k1").Result() 129 | assert.NoError(suite.T(), err) 130 | assert.Equal(suite.T(), res, int64(1)) 131 | 132 | result, err := suite.tairClient.ExHGet(ctx, "k1", "f1").Result() 133 | assert.NoError(suite.T(), err) 134 | assert.Equal(suite.T(), result, "v1") 135 | } 136 | 137 | func (suite *TairClusterTestSuite) TestClusterExZAdd() { 138 | res, err := suite.tairClient.ExZAdd(ctx, "k1", "90.1", "v1").Result() 139 | assert.NoError(suite.T(), err) 140 | assert.Equal(suite.T(), res, int64(1)) 141 | 142 | zRangeRes, err := suite.tairClient.ExZRange(ctx, "k1", 0, -1).Result() 143 | assert.NoError(suite.T(), err) 144 | assert.Equal(suite.T(), zRangeRes[0], "v1") 145 | 146 | res, err = suite.tairClient.ExZAdd(ctx, "foo", "1", "a").Result() 147 | assert.NoError(suite.T(), err) 148 | assert.Equal(suite.T(), res, int64(1)) 149 | 150 | res, err = suite.tairClient.ExZAdd(ctx, "foo", "10", "b").Result() 151 | assert.NoError(suite.T(), err) 152 | assert.Equal(suite.T(), res, int64(1)) 153 | 154 | res, err = suite.tairClient.ExZAdd(ctx, "foo", "2", "a").Result() 155 | assert.NoError(suite.T(), err) 156 | assert.Equal(suite.T(), res, int64(0)) 157 | } 158 | 159 | func (suite *TairZsetTestSuite) TestClusterExZAddParams() { 160 | res, err := suite.tairClient.ExZAddArgs(ctx, "foo", "1", "a", tair.ExZAddArgs{}.New().Xx()).Result() 161 | assert.NoError(suite.T(), err) 162 | assert.Equal(suite.T(), res, int64(0)) 163 | 164 | res, err = suite.tairClient.ExZAdd(ctx, "foo", "1", "a").Result() 165 | assert.NoError(suite.T(), err) 166 | assert.Equal(suite.T(), res, int64(1)) 167 | 168 | res, err = suite.tairClient.ExZAddArgs(ctx, "foo", "2", "a", tair.ExZAddArgs{}.New().Nx()).Result() 169 | assert.NoError(suite.T(), err) 170 | assert.Equal(suite.T(), res, int64(0)) 171 | 172 | res, err = suite.tairClient.ExZAddManyMemberArgs(ctx, "foo", tair.ExZAddArgs{}.New().Ch(), 173 | tair.ExZAddMember{Score: "2", Member: "a"}, tair.ExZAddMember{Score: "1", Member: "b"}).Result() 174 | assert.NoError(suite.T(), err) 175 | assert.Equal(suite.T(), res, int64(2)) 176 | } 177 | 178 | func (suite *TairClusterTestSuite) TestClusterTFTAnalyzer() { 179 | suite.tairClient.TftCreateIndex(ctx, "tftkey", "{\"mappings\":{\"properties\":{\"f0\":{\"type\":\"text\",\"analyzer\":\"my_analyzer\"}}},\"settings\":{\"analysis\":{\"analyzer\":{\"my_analyzer\":{\"type\":\"standard\"}}}}}") 180 | text := "This is tair-go." 181 | a1 := tair.TftAnalyzerArgs{}.New().Index("tftkey") 182 | result1, err1 := suite.tairClient.TftAnalyzerWithArgs(ctx, "my_analyzer", text, a1).Result() 183 | assert.NoError(suite.T(), err1) 184 | result2, err2 := suite.tairClient.TftAnalyzer(ctx, "standard", text).Result() 185 | assert.NoError(suite.T(), err2) 186 | assert.Equal(suite.T(), result1, result2) 187 | 188 | a2 := tair.TftAnalyzerArgs{}.New().ShowTime() 189 | r3, err3 := suite.tairClient.TftAnalyzerWithArgs(ctx, "standard", text, a2).Result() 190 | assert.NoError(suite.T(), err3) 191 | assert.Contains(suite.T(), r3, "consuming time") 192 | } 193 | 194 | func (suite *TairClusterTestSuite) TestClusterftMSearch() { 195 | suite.tairClient.TftCreateIndex(ctx, "{tftkey}1", "{\"mappings\":{\"dynamic\":\"false\",\"properties\":{\"f0\":{\"type\":\"text\"},\"f1\":{\"type\":\"text\"}}}}") 196 | suite.tairClient.TftCreateIndex(ctx, "{tftkey}2", "{\"mappings\":{\"dynamic\":\"false\",\"properties\":{\"f0\":{\"type\":\"text\"},\"f1\":{\"type\":\"text\"}}}}") 197 | suite.tairClient.TftCreateIndex(ctx, "{tftkey}3", "{\"mappings\":{\"dynamic\":\"false\",\"properties\":{\"f0\":{\"type\":\"text\"},\"f1\":{\"type\":\"text\"}}}}") 198 | suite.tairClient.TftAddDocWithId(ctx, "{tftkey}1", "{\"f0\":\"v0\",\"f1\":\"3\"}", "1") 199 | suite.tairClient.TftAddDocWithId(ctx, "{tftkey}2", "{\"f0\":\"v1\",\"f1\":\"3\"}", "2") 200 | suite.tairClient.TftAddDocWithId(ctx, "{tftkey}3", "{\"f0\":\"v3\",\"f1\":\"3\"}", "3") 201 | suite.tairClient.TftAddDocWithId(ctx, "{tftkey}1", "{\"f0\":\"v3\",\"f1\":\"4\"}", "4") 202 | suite.tairClient.TftAddDocWithId(ctx, "{tftkey}2", "{\"f0\":\"v3\",\"f1\":\"5\"}", "5") 203 | 204 | result1, err1 := suite.tairClient.TftMSearch(ctx, 3, "{\"query\":{\"match\":{\"f1\":\"3\"}}}", "{tftkey}1", "{tftkey}2", "{tftkey}3").Result() 205 | assert.NoError(suite.T(), err1) 206 | assert.Equal(suite.T(), result1, "{\"hits\":{\"hits\":[{\"_id\":\"1\",\"_index\":\"{tftkey}1\",\"_score\":1.0,\"_source\":{\"f0\":\"v0\",\"f1\":\"3\"}},{\"_id\":\"2\",\"_index\":\"{tftkey}2\",\"_score\":1.0,\"_source\":{\"f0\":\"v1\",\"f1\":\"3\"}},{\"_id\":\"3\",\"_index\":\"{tftkey}3\",\"_score\":0.094159,\"_source\":{\"f0\":\"v3\",\"f1\":\"3\"}}],\"max_score\":1.0,\"total\":{\"relation\":\"eq\",\"value\":3}},\"aux_info\":{\"index_crc64\":52600736426816810}}") 207 | } 208 | 209 | func TestTairClusterTestSuite(t *testing.T) { 210 | suite.Run(t, new(TairClusterTestSuite)) 211 | } 212 | -------------------------------------------------------------------------------- /tair/taircpc.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | type CpcData struct { 12 | arg 13 | key string 14 | item string 15 | expStr string 16 | exp int64 17 | hasSetExp bool 18 | } 19 | 20 | func (a *CpcData) ExpStr() string { 21 | return a.expStr 22 | } 23 | 24 | func (a *CpcData) Exp() int64 { 25 | return a.exp 26 | } 27 | 28 | func (a *CpcData) Key() string { 29 | return a.key 30 | } 31 | 32 | func (a *CpcData) SetKey(key string) { 33 | a.key = key 34 | } 35 | 36 | func (a *CpcData) Item() string { 37 | return a.item 38 | } 39 | 40 | func (a *CpcData) SetItem(item string) { 41 | a.item = item 42 | } 43 | 44 | func (a *CpcData) New() *CpcData { 45 | a.Set = make(map[string]bool) 46 | return a 47 | } 48 | 49 | func (a *CpcData) ex(secondsToExpire int64) *CpcData { 50 | if a.hasSetExp { 51 | panic(ExpIsSet) 52 | } 53 | a.hasSetExp = true 54 | a.Set[EX] = true 55 | a.exp = secondsToExpire 56 | return a 57 | } 58 | 59 | func (a *CpcData) px(millisecondsToExpire int64) *CpcData { 60 | if a.hasSetExp { 61 | panic(ExpIsSet) 62 | } 63 | a.hasSetExp = true 64 | a.Set[PX] = true 65 | a.exp = millisecondsToExpire 66 | return a 67 | } 68 | 69 | func (a *CpcData) exAt(secondsToExpire int64) *CpcData { 70 | if a.hasSetExp { 71 | panic(ExpIsSet) 72 | } 73 | a.hasSetExp = true 74 | a.Set[EXAT] = true 75 | a.exp = secondsToExpire 76 | return a 77 | } 78 | 79 | func (a *CpcData) pxAt(millisecondsToExpire int64) *CpcData { 80 | if a.hasSetExp { 81 | panic(ExpIsSet) 82 | } 83 | a.hasSetExp = true 84 | a.Set[PXAT] = true 85 | a.exp = millisecondsToExpire 86 | return a 87 | } 88 | 89 | type CpcUpdateArgs struct { 90 | arg 91 | ex time.Duration 92 | exAt time.Time 93 | px time.Duration 94 | pxAt time.Time 95 | size int64 96 | winSize int64 97 | } 98 | 99 | func (a CpcUpdateArgs) New() *CpcUpdateArgs { 100 | a.Set = make(map[string]bool) 101 | return &a 102 | } 103 | 104 | func (a *CpcUpdateArgs) Ex() time.Duration { 105 | return a.ex 106 | } 107 | 108 | func (a *CpcUpdateArgs) SetEx(ex time.Duration) { 109 | a.Set[EX] = true 110 | a.ex = ex 111 | } 112 | 113 | func (a *CpcUpdateArgs) ExAt() time.Time { 114 | return a.exAt 115 | } 116 | 117 | func (a *CpcUpdateArgs) SetExAt(exAt time.Time) { 118 | a.Set[EXAT] = true 119 | a.exAt = exAt 120 | } 121 | 122 | func (a *CpcUpdateArgs) Px() time.Duration { 123 | return a.px 124 | } 125 | 126 | func (a *CpcUpdateArgs) SetPx(px time.Duration) { 127 | a.Set[PX] = true 128 | a.px = px 129 | } 130 | 131 | func (a *CpcUpdateArgs) PxAt() time.Time { 132 | return a.pxAt 133 | } 134 | 135 | func (a *CpcUpdateArgs) SetPxAt(pxAt time.Time) { 136 | a.Set[PXAT] = true 137 | a.pxAt = pxAt 138 | } 139 | 140 | func (a *CpcUpdateArgs) Size() int64 { 141 | return a.size 142 | } 143 | 144 | func (a *CpcUpdateArgs) SetSize(size int64) { 145 | a.Set[SIZE] = true 146 | a.size = size 147 | } 148 | 149 | func (a *CpcUpdateArgs) WinSize() int64 { 150 | return a.winSize 151 | } 152 | 153 | func (a *CpcUpdateArgs) SetWinSize(winSize int64) { 154 | a.Set[WIN] = true 155 | a.winSize = winSize 156 | } 157 | 158 | func (a *CpcUpdateArgs) GetArgs() []interface{} { 159 | args := make([]interface{}, 0) 160 | 161 | if _, ok := a.Set[EXAT]; ok { 162 | args = append(args, EXAT, a.exAt.Unix()) 163 | } 164 | if _, ok := a.Set[PXAT]; ok { 165 | args = append(args, PXAT, a.pxAt.Unix()) 166 | } 167 | if _, ok := a.Set[EX]; ok { 168 | args = append(args, EX, toSec(a.ex)) 169 | } 170 | if _, ok := a.Set[PX]; ok { 171 | args = append(args, PX, toMs(a.px)) 172 | } 173 | if _, ok := a.Set[WIN]; ok { 174 | args = append(args, WIN, a.winSize) 175 | } 176 | if _, ok := a.Set[SIZE]; ok { 177 | args = append(args, SIZE, a.size) 178 | } 179 | return args 180 | } 181 | 182 | type CpcMultiUpdateArgs struct { 183 | arg 184 | } 185 | 186 | func (a CpcMultiUpdateArgs) New() *CpcMultiUpdateArgs { 187 | a.Set = make(map[string]bool) 188 | return &a 189 | } 190 | 191 | func (a CpcMultiUpdateArgs) JoinArgs(cpcData []CpcData) []interface{} { 192 | args := make([]interface{}, 0) 193 | for _, data := range cpcData { 194 | args = append(args, data.Key(), data.Item(), data.ExpStr(), data.Exp()) 195 | } 196 | return args 197 | } 198 | 199 | type Update2JudCmd struct { 200 | *redis.SliceCmd 201 | value float64 202 | diffValue float64 203 | } 204 | 205 | func (cmd *Update2JudCmd) Value() float64 { 206 | val := cmd.SliceCmd.Val() 207 | var err error 208 | cmd.value, err = strconv.ParseFloat(val[0].(string), 64) 209 | if err != nil { 210 | panic("cannot parse float") 211 | } 212 | return cmd.value 213 | } 214 | 215 | func (cmd *Update2JudCmd) SetValue(value float64) { 216 | cmd.value = value 217 | } 218 | 219 | func (cmd *Update2JudCmd) DiffValue() float64 { 220 | val := cmd.SliceCmd.Val() 221 | var err error 222 | cmd.diffValue, err = strconv.ParseFloat(val[1].(string), 64) 223 | if err != nil { 224 | panic("cannot parse float") 225 | } 226 | return cmd.diffValue 227 | } 228 | 229 | func (cmd *Update2JudCmd) SetDiffValue(diffValue float64) { 230 | cmd.diffValue = diffValue 231 | } 232 | 233 | func (cmd *Update2JudCmd) Result() (*Update2JudCmd, error) { 234 | return cmd, cmd.SliceCmd.Err() 235 | } 236 | 237 | func NewUpdate2JudCmd(ctx context.Context, arg ...interface{}) *Update2JudCmd { 238 | return &Update2JudCmd{ 239 | SliceCmd: redis.NewSliceCmd(ctx, arg...), 240 | } 241 | } 242 | 243 | func (tc tairCmdable) CpcUpdate(ctx context.Context, key string, item string) *redis.StringCmd { 244 | if key == "" { 245 | panic(KeyIsEmpty) 246 | } 247 | if item == "" { 248 | panic(ValueIsEmpty) 249 | } 250 | a := make([]interface{}, 3) 251 | a[0] = "CPC.UPDATE" 252 | a[1] = key 253 | a[2] = item 254 | cmd := redis.NewStringCmd(ctx, a...) 255 | _ = tc(ctx, cmd) 256 | return cmd 257 | } 258 | 259 | func (tc tairCmdable) CpcEstimate(ctx context.Context, key string) *redis.FloatCmd { 260 | if key == "" { 261 | panic(KeyIsEmpty) 262 | } 263 | a := make([]interface{}, 2) 264 | a[0] = "CPC.ESTIMATE" 265 | a[1] = key 266 | cmd := redis.NewFloatCmd(ctx, a...) 267 | _ = tc(ctx, cmd) 268 | return cmd 269 | } 270 | 271 | func (tc tairCmdable) CpcUpdateArgs(ctx context.Context, key string, item string, args *CpcUpdateArgs) *redis.StringCmd { 272 | if key == "" { 273 | panic(KeyIsEmpty) 274 | } 275 | if item == "" { 276 | panic(ValueIsEmpty) 277 | } 278 | a := make([]interface{}, 3) 279 | a[0] = "CPC.UPDATE" 280 | a[1] = key 281 | a[2] = item 282 | a = append(a, args.GetArgs()...) 283 | cmd := redis.NewStringCmd(ctx, a...) 284 | _ = tc(ctx, cmd) 285 | return cmd 286 | } 287 | 288 | func (tc tairCmdable) CpcUpdate2Est(ctx context.Context, key string, item string) *redis.FloatCmd { 289 | if key == "" { 290 | panic(KeyIsEmpty) 291 | } 292 | if item == "" { 293 | panic(ValueIsEmpty) 294 | } 295 | a := make([]interface{}, 3) 296 | a[0] = "CPC.UPDATE2EST" 297 | a[1] = key 298 | a[2] = item 299 | cmd := redis.NewFloatCmd(ctx, a...) 300 | _ = tc(ctx, cmd) 301 | return cmd 302 | } 303 | 304 | func (tc tairCmdable) CpcUpdate2EstArgs(ctx context.Context, key string, item string, args CpcUpdateArgs) *redis.FloatCmd { 305 | if key == "" { 306 | panic(KeyIsEmpty) 307 | } 308 | if item == "" { 309 | panic(ValueIsEmpty) 310 | } 311 | a := make([]interface{}, 3) 312 | a[0] = "CPC.UPDATE2EST" 313 | a[1] = key 314 | a[2] = item 315 | a = append(a, args.GetArgs()...) 316 | cmd := redis.NewFloatCmd(ctx, a...) 317 | _ = tc(ctx, cmd) 318 | return cmd 319 | } 320 | 321 | func (tc tairCmdable) CpcUpdate2Jud(ctx context.Context, key string, item string) *Update2JudCmd { 322 | if key == "" { 323 | panic(KeyIsEmpty) 324 | } 325 | if item == "" { 326 | panic(ValueIsEmpty) 327 | } 328 | a := make([]interface{}, 3) 329 | a[0] = "CPC.UPDATE2JUD" 330 | a[1] = key 331 | a[2] = item 332 | cmd := NewUpdate2JudCmd(ctx, a...) 333 | _ = tc(ctx, cmd) 334 | return cmd 335 | } 336 | 337 | func (tc tairCmdable) CpcUpdate2JudArgs(ctx context.Context, key string, item string, args CpcUpdateArgs) *Update2JudCmd { 338 | if key == "" { 339 | panic(KeyIsEmpty) 340 | } 341 | if item == "" { 342 | panic(ValueIsEmpty) 343 | } 344 | a := make([]interface{}, 3) 345 | a[0] = "CPC.UPDATE" 346 | a[1] = key 347 | a[2] = item 348 | a = append(a, args.GetArgs()...) 349 | cmd := NewUpdate2JudCmd(ctx, a...) 350 | _ = tc(ctx, cmd) 351 | return cmd 352 | } 353 | 354 | // array 355 | func (tc tairCmdable) CpcArrayUpdate(ctx context.Context, key string, timestamp int64, item string) *redis.StringCmd { 356 | if key == "" { 357 | panic(KeyIsEmpty) 358 | } 359 | if item == "" { 360 | panic(ValueIsEmpty) 361 | } 362 | a := make([]interface{}, 4) 363 | a[0] = "CPC.ARRAY.UPDATE" 364 | a[1] = key 365 | a[2] = timestamp 366 | a[3] = item 367 | cmd := redis.NewStringCmd(ctx, a...) 368 | _ = tc(ctx, cmd) 369 | return cmd 370 | } 371 | 372 | func (tc tairCmdable) CpcArrayUpdateArgs(ctx context.Context, key string, timestamp int64, item string, args CpcUpdateArgs) *redis.FloatCmd { 373 | if key == "" { 374 | panic(KeyIsEmpty) 375 | } 376 | if item == "" { 377 | panic(ValueIsEmpty) 378 | } 379 | a := make([]interface{}, 4) 380 | a[0] = "CPC.ARRAY.UPDATE" 381 | a[1] = key 382 | a[2] = timestamp 383 | a[3] = item 384 | a = append(a, args.GetArgs()...) 385 | cmd := redis.NewFloatCmd(ctx, a...) 386 | _ = tc(ctx, cmd) 387 | return cmd 388 | } 389 | 390 | func (tc tairCmdable) CpcArrayEstimate(ctx context.Context, key string, timestamp int64) *redis.FloatCmd { 391 | if key == "" { 392 | panic(KeyIsEmpty) 393 | } 394 | a := make([]interface{}, 3) 395 | a[0] = "CPC.ARRAY.ESTIMATE" 396 | a[1] = key 397 | a[2] = timestamp 398 | cmd := redis.NewFloatCmd(ctx, a...) 399 | _ = tc(ctx, cmd) 400 | return cmd 401 | } 402 | 403 | func (tc tairCmdable) CpcArrayEstimateRange(ctx context.Context, key string, startTime int64, endTime int64) *redis.FloatSliceCmd { 404 | if key == "" { 405 | panic(KeyIsEmpty) 406 | } 407 | a := make([]interface{}, 4) 408 | a[0] = "CPC.ARRAY.ESTIMATE.RANGE" 409 | a[1] = key 410 | a[2] = startTime 411 | a[3] = endTime 412 | cmd := redis.NewFloatSliceCmd(ctx, a...) 413 | _ = tc(ctx, cmd) 414 | return cmd 415 | } 416 | 417 | func (tc tairCmdable) CpcArrayEstimateRangeMerge(ctx context.Context, key string, timestamp int64, estRange int64) *redis.FloatCmd { 418 | if key == "" { 419 | panic(KeyIsEmpty) 420 | } 421 | a := make([]interface{}, 4) 422 | a[0] = "CPC.ARRAY.ESTIMATE.RANGE.SUM" 423 | a[1] = key 424 | a[2] = timestamp 425 | a[3] = estRange 426 | cmd := redis.NewFloatCmd(ctx, a...) 427 | _ = tc(ctx, cmd) 428 | return cmd 429 | } 430 | 431 | func (tc tairCmdable) CpcArrayUpdate2Est(ctx context.Context, key string, timestamp int64, item string) *redis.FloatCmd { 432 | if key == "" { 433 | panic(KeyIsEmpty) 434 | } 435 | if item == "" { 436 | panic(ValueIsEmpty) 437 | } 438 | a := make([]interface{}, 4) 439 | a[0] = "CPC.ARRAY.UPDATE2EST" 440 | a[1] = key 441 | a[2] = timestamp 442 | a[3] = item 443 | cmd := redis.NewFloatCmd(ctx, a...) 444 | _ = tc(ctx, cmd) 445 | return cmd 446 | } 447 | 448 | func (tc tairCmdable) CpcArrayUpdate2EstArgs(ctx context.Context, key string, timestamp int64, item string, args CpcUpdateArgs) *redis.FloatCmd { 449 | if key == "" { 450 | panic(KeyIsEmpty) 451 | } 452 | if item == "" { 453 | panic(ValueIsEmpty) 454 | } 455 | a := make([]interface{}, 4) 456 | a[0] = "CPC.ARRAY.UPDATE2EST" 457 | a[1] = key 458 | a[2] = timestamp 459 | a[3] = item 460 | a = append(a, args.GetArgs()...) 461 | cmd := redis.NewFloatCmd(ctx, a...) 462 | _ = tc(ctx, cmd) 463 | return cmd 464 | } 465 | 466 | func (tc tairCmdable) CpcArrayUpdate2Jud(ctx context.Context, key string, timestamp int64, item string) *Update2JudCmd { 467 | if key == "" { 468 | panic(KeyIsEmpty) 469 | } 470 | if item == "" { 471 | panic(ValueIsEmpty) 472 | } 473 | a := make([]interface{}, 4) 474 | a[0] = "CPC.ARRAY.UPDATE2JUD" 475 | a[1] = key 476 | a[2] = timestamp 477 | a[3] = item 478 | cmd := NewUpdate2JudCmd(ctx, a...) 479 | _ = tc(ctx, cmd) 480 | return cmd 481 | } 482 | 483 | func (tc tairCmdable) CpcArrayUpdate2JudArgs(ctx context.Context, key string, timestamp int64, item string, args CpcUpdateArgs) *Update2JudCmd { 484 | if key == "" { 485 | panic(KeyIsEmpty) 486 | } 487 | if item == "" { 488 | panic(ValueIsEmpty) 489 | } 490 | a := make([]interface{}, 4) 491 | a[0] = "CPC.ARRAY.UPDATE2JUD" 492 | a[1] = key 493 | a[2] = timestamp 494 | a[3] = item 495 | a = append(a, args.GetArgs()...) 496 | cmd := NewUpdate2JudCmd(ctx, a...) 497 | _ = tc(ctx, cmd) 498 | return cmd 499 | } 500 | 501 | // end 502 | -------------------------------------------------------------------------------- /tair/taircpc_test.go: -------------------------------------------------------------------------------- 1 | package tair_test 2 | 3 | import ( 4 | "github.com/alibaba/tair-go/tair" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/stretchr/testify/suite" 7 | "math/rand" 8 | "strconv" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | var key string = "key" + randStrCpc(20) 14 | var key2 string = "key2" + randStrCpc(20) 15 | var key3 string = "key3" + randStrCpc(20) 16 | var item string = "item" + randStrCpc(20) 17 | var item2 string = "item2" + randStrCpc(20) 18 | var item3 string = "item3" + randStrCpc(20) 19 | var item4 string = "item4" + randStrCpc(20) 20 | var content1 string = "content1" + randStrCpc(20) 21 | var content2 string = "content2" + randStrCpc(20) 22 | 23 | var count1 int64 = 100 24 | var count2 int64 = 200 25 | var timestamp int64 = 1000000 26 | var winSize int64 = 6000 27 | 28 | func randStrCpc(size int) string { 29 | str := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 30 | bytes := []byte(str) 31 | var result []byte 32 | rand.Seed(time.Now().UnixNano() + int64(rand.Intn(100000))) 33 | for i := 0; i < size; i++ { 34 | result = append(result, bytes[rand.Intn(len(bytes))]) 35 | } 36 | return string(result) 37 | } 38 | 39 | type TairCpcTestSuite struct { 40 | suite.Suite 41 | tairClient *tair.TairClient 42 | } 43 | 44 | func (suite *TairCpcTestSuite) SetupTest() { 45 | suite.tairClient = tair.NewTairClient(redisOptions()) 46 | assert.Equal(suite.T(), "OK", suite.tairClient.FlushDB(ctx).Val()) 47 | } 48 | 49 | func (suite *TairCpcTestSuite) TearDownTest() { 50 | assert.NoError(suite.T(), suite.tairClient.Close()) 51 | } 52 | 53 | func (suite *TairCpcTestSuite) TestCpcUpdate() { 54 | r, e := suite.tairClient.CpcUpdate(ctx, key, item).Result() 55 | assert.NoError(suite.T(), e) 56 | assert.Equal(suite.T(), r, "OK") 57 | } 58 | 59 | func (suite *TairCpcTestSuite) TestCpcUpdateExpire() { 60 | args := tair.CpcUpdateArgs{}.New() 61 | args.SetEx(2) 62 | r, e := suite.tairClient.CpcUpdateArgs(ctx, key, item, args).Result() 63 | assert.NoError(suite.T(), e) 64 | assert.Equal(suite.T(), r, "OK") 65 | 66 | r1, e1 := suite.tairClient.CpcUpdate(ctx, key, item2).Result() 67 | assert.NoError(suite.T(), e1) 68 | assert.Equal(suite.T(), r1, "OK") 69 | 70 | r2, e2 := suite.tairClient.CpcEstimate(ctx, key).Result() 71 | assert.NoError(suite.T(), e2) 72 | assert.InDelta(suite.T(), r2, 2.00, 0.001) 73 | 74 | time.Sleep(2 * time.Second) 75 | 76 | _, e3 := suite.tairClient.CpcEstimate(ctx, key).Result() 77 | assert.Error(suite.T(), e3) 78 | 79 | r4, e4 := suite.tairClient.CpcUpdate(ctx, key, item).Result() 80 | assert.NoError(suite.T(), e4) 81 | assert.Equal(suite.T(), r4, "OK") 82 | 83 | args.SetEx(0) 84 | r5, e5 := suite.tairClient.CpcUpdateArgs(ctx, key, item2, args).Result() 85 | assert.NoError(suite.T(), e5) 86 | assert.Equal(suite.T(), r5, "OK") 87 | 88 | time.Sleep(1000) 89 | r6, e6 := suite.tairClient.CpcEstimate(ctx, key).Result() 90 | assert.NoError(suite.T(), e6) 91 | assert.InDelta(suite.T(), r6, 2, 0.001) 92 | 93 | args.SetEx(2) 94 | r7, e7 := suite.tairClient.CpcUpdateArgs(ctx, key, item2, args).Result() 95 | assert.NoError(suite.T(), e7) 96 | assert.Equal(suite.T(), r7, "OK") 97 | 98 | args.SetEx(-1) 99 | r8, e8 := suite.tairClient.CpcUpdateArgs(ctx, key, item2, args).Result() 100 | assert.NoError(suite.T(), e8) 101 | assert.Equal(suite.T(), r8, "OK") 102 | 103 | time.Sleep(2000) 104 | r9, e9 := suite.tairClient.CpcEstimate(ctx, key).Result() 105 | assert.NoError(suite.T(), e9) 106 | assert.InDelta(suite.T(), r9, 2, 0.001) 107 | } 108 | 109 | func (suite *TairCpcTestSuite) TestCpcUpdate2Jud() { 110 | r, e := suite.tairClient.CpcUpdate(ctx, key, item).Result() 111 | assert.NoError(suite.T(), e) 112 | assert.Equal(suite.T(), r, "OK") 113 | 114 | r1, e1 := suite.tairClient.CpcUpdate(ctx, key, item2).Result() 115 | assert.NoError(suite.T(), e1) 116 | assert.Equal(suite.T(), r1, "OK") 117 | 118 | r2, e2 := suite.tairClient.CpcUpdate(ctx, key, item3).Result() 119 | assert.NoError(suite.T(), e2) 120 | assert.Equal(suite.T(), r2, "OK") 121 | 122 | r3, e3 := suite.tairClient.CpcEstimate(ctx, key).Result() 123 | assert.NoError(suite.T(), e3) 124 | assert.InDelta(suite.T(), r3, 3.00, 0.001) 125 | 126 | r4, e4 := suite.tairClient.CpcUpdate2Jud(ctx, key, item).Result() 127 | assert.NoError(suite.T(), e4) 128 | assert.InDelta(suite.T(), r4.Value(), 3, 0.001) 129 | assert.InDelta(suite.T(), r4.DiffValue(), 0.0, 0.001) 130 | 131 | r5, e5 := suite.tairClient.CpcUpdate2Jud(ctx, key, item4).Result() 132 | assert.NoError(suite.T(), e5) 133 | assert.InDelta(suite.T(), r5.Value(), 4, 0.001) 134 | assert.InDelta(suite.T(), r5.DiffValue(), 1, 0.001) 135 | } 136 | func (suite *TairCpcTestSuite) TestCpcAccurateEstimation() { 137 | for i := 0; i < 120; i++ { 138 | itemTemp := item + strconv.Itoa(i) 139 | r1, e1 := suite.tairClient.CpcUpdate(ctx, key, itemTemp).Result() 140 | assert.NoError(suite.T(), e1) 141 | assert.Equal(suite.T(), r1, "OK") 142 | } 143 | r, e := suite.tairClient.CpcEstimate(ctx, key).Result() 144 | assert.NoError(suite.T(), e) 145 | assert.InDelta(suite.T(), r, 120, 0.1) 146 | } 147 | 148 | func (suite *TairCpcTestSuite) TestCpcUpdate2Est() { 149 | for i := 0; i < 120; i++ { 150 | itemTemp := item + strconv.Itoa(i) 151 | r1, e1 := suite.tairClient.CpcUpdate2Est(ctx, key, itemTemp).Result() 152 | assert.NoError(suite.T(), e1) 153 | assert.InDelta(suite.T(), r1, i+1, float64(i)+0.1) 154 | } 155 | r, e := suite.tairClient.CpcEstimate(ctx, key).Result() 156 | assert.NoError(suite.T(), e) 157 | assert.InDelta(suite.T(), r, 120, 0.1) 158 | } 159 | 160 | func (suite *TairCpcTestSuite) TestCpcArrayUpdateAndEstimate() { 161 | r, e := suite.tairClient.CpcArrayUpdate(ctx, key, 1, item).Result() 162 | assert.NoError(suite.T(), e) 163 | assert.Equal(suite.T(), r, "OK") 164 | 165 | r1, e2 := suite.tairClient.CpcArrayUpdate(ctx, key, 1, item2).Result() 166 | assert.NoError(suite.T(), e2) 167 | assert.Equal(suite.T(), r1, "OK") 168 | 169 | r3, e3 := suite.tairClient.CpcArrayUpdate(ctx, key, 3, item).Result() 170 | assert.NoError(suite.T(), e3) 171 | assert.Equal(suite.T(), r3, "OK") 172 | 173 | r4, e4 := suite.tairClient.CpcArrayUpdate(ctx, key, 5, item).Result() 174 | assert.NoError(suite.T(), e4) 175 | assert.Equal(suite.T(), r4, "OK") 176 | 177 | r5, e5 := suite.tairClient.CpcArrayEstimate(ctx, key, 1).Result() 178 | assert.NoError(suite.T(), e5) 179 | assert.InDelta(suite.T(), r5, 2, 0.001) 180 | 181 | r6, e6 := suite.tairClient.CpcArrayEstimate(ctx, key, 3).Result() 182 | assert.NoError(suite.T(), e6) 183 | assert.InDelta(suite.T(), r6, 2, 0.001) 184 | 185 | r7, e7 := suite.tairClient.CpcArrayEstimate(ctx, key, 5).Result() 186 | assert.NoError(suite.T(), e7) 187 | assert.InDelta(suite.T(), r7, 2, 0.001) 188 | } 189 | 190 | //func (suite *TairCpcTestSuite) TestCpcArrayEstimateRange() { 191 | // r, e := suite.tairClient.CpcArrayUpdate(ctx, key, timestamp, item).Result() 192 | // assert.NoError(suite.T(), e) 193 | // assert.Equal(suite.T(), r, "OK") 194 | // 195 | // r1, e1 := suite.tairClient.CpcArrayEstimateRange(ctx, key, timestamp-1000, timestamp+1000).Result() 196 | // assert.NoError(suite.T(), e1) 197 | // assert.InDelta(suite.T(), r1[0], 1, 0.1) 198 | //} 199 | // 200 | //func (suite *TairCpcTestSuite) TestCpcArrayEstimateRangeMerge() { 201 | // r, e := suite.tairClient.CpcArrayUpdate(ctx, key, timestamp, item).Result() 202 | // assert.NoError(suite.T(), e) 203 | // assert.Equal(suite.T(), r, "OK") 204 | // 205 | // r1, e1 := suite.tairClient.CpcArrayEstimateRange(ctx, key, timestamp-1000, timestamp+1000).Result() 206 | // assert.NoError(suite.T(), e1) 207 | // assert.InDelta(suite.T(), r1[0], 1, 0.1) 208 | // 209 | // r2, e2 := suite.tairClient.CpcArrayEstimateRangeMerge(ctx, key, timestamp, 10000).Result() 210 | // assert.NoError(suite.T(), e2) 211 | // assert.InDelta(suite.T(), r2, 1, 0.1) 212 | //} 213 | // 214 | //func (suite *TairCpcTestSuite) TestCpcArrayUpdate2Est() { 215 | // r, e := suite.tairClient.CpcArrayUpdate(ctx, key, timestamp, item).Result() 216 | // assert.NoError(suite.T(), e) 217 | // assert.Equal(suite.T(), r, "OK") 218 | // r1, e1 := suite.tairClient.CpcArrayEstimateRange(ctx, key, timestamp-1000, timestamp+1000).Result() 219 | // assert.NoError(suite.T(), e1) 220 | // assert.InDelta(suite.T(), r1[0], 1, 0.001) 221 | //} 222 | 223 | func (suite *TairCpcTestSuite) TestCpcArrayUpdate2Jud() { 224 | r, e := suite.tairClient.CpcUpdate(ctx, key, item).Result() 225 | assert.NoError(suite.T(), e) 226 | assert.Equal(suite.T(), r, "OK") 227 | 228 | r1, e1 := suite.tairClient.CpcUpdate(ctx, key, item2).Result() 229 | assert.NoError(suite.T(), e1) 230 | assert.Equal(suite.T(), r1, "OK") 231 | 232 | r2, e2 := suite.tairClient.CpcUpdate(ctx, key, item3).Result() 233 | assert.NoError(suite.T(), e2) 234 | assert.Equal(suite.T(), r2, "OK") 235 | 236 | r3, e3 := suite.tairClient.CpcEstimate(ctx, key).Result() 237 | assert.NoError(suite.T(), e3) 238 | assert.InDelta(suite.T(), r3, 3, 0.001) 239 | 240 | r4, e4 := suite.tairClient.CpcUpdate2Jud(ctx, key, item).Result() 241 | assert.NoError(suite.T(), e4) 242 | assert.InDelta(suite.T(), r4.Value(), 3.00, 0.001) 243 | assert.InDelta(suite.T(), r4.DiffValue(), 0.00, 0.001) 244 | 245 | r5, e5 := suite.tairClient.CpcUpdate2Jud(ctx, key, item4).Result() 246 | assert.NoError(suite.T(), e5) 247 | assert.InDelta(suite.T(), r5.Value(), 4.00, 0.001) 248 | assert.InDelta(suite.T(), r5.DiffValue(), 1.00, 0.001) 249 | } 250 | 251 | func TestTairCpcTestSuite(t *testing.T) { 252 | suite.Run(t, new(TairCpcTestSuite)) 253 | } 254 | -------------------------------------------------------------------------------- /tair/tairdoc.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/redis/go-redis/v9" 7 | ) 8 | 9 | // args 10 | type JsonSetArgs struct { 11 | arg 12 | } 13 | 14 | func (a JsonSetArgs) New() *JsonSetArgs { 15 | a.Set = make(map[string]bool, 0) 16 | return &a 17 | } 18 | func (a *JsonSetArgs) Xx() *JsonSetArgs { 19 | a.Set[XX] = true 20 | return a 21 | } 22 | 23 | func (a *JsonSetArgs) Nx() *JsonSetArgs { 24 | a.Set[NX] = true 25 | return a 26 | } 27 | 28 | func (a *JsonSetArgs) GetArgs() []interface{} { 29 | args := make([]interface{}, 0) 30 | if _, ok := a.Set[XX]; ok { 31 | args = append(args, XX) 32 | } 33 | if _, ok := a.Set[NX]; ok { 34 | args = append(args, NX) 35 | } 36 | return args 37 | } 38 | 39 | type JsonGetArgs struct { 40 | arg 41 | format string 42 | rootName string 43 | arrName string 44 | } 45 | 46 | func (a *JsonGetArgs) Format(format string) *JsonGetArgs { 47 | a.Set[FORMAT] = true 48 | a.format = format 49 | return a 50 | } 51 | 52 | func (a *JsonGetArgs) RootName(rootName string) *JsonGetArgs { 53 | a.Set[ROOTNAME] = true 54 | a.rootName = rootName 55 | return a 56 | } 57 | 58 | func (a *JsonGetArgs) ArrName(arrName string) *JsonGetArgs { 59 | a.Set[ARRNAME] = true 60 | a.arrName = arrName 61 | return a 62 | } 63 | 64 | func (a JsonGetArgs) New() *JsonGetArgs { 65 | a.Set = make(map[string]bool, 0) 66 | return &a 67 | } 68 | 69 | func (a *JsonGetArgs) GetArgs() []interface{} { 70 | args := make([]interface{}, 0) 71 | if _, ok := a.Set[FORMAT]; ok { 72 | args = append(args, FORMAT, a.format) 73 | } 74 | if _, ok := a.Set[ARRNAME]; ok { 75 | args = append(args, ARRNAME, a.arrName) 76 | } 77 | if _, ok := a.Set[ROOTNAME]; ok { 78 | args = append(args, ROOTNAME, a.rootName) 79 | } 80 | return args 81 | } 82 | 83 | func (tc tairCmdable) JsonSet(ctx context.Context, key string, path string, json string) *redis.StringCmd { 84 | a := make([]interface{}, 4) 85 | a[0] = "JSON.SET" 86 | a[1] = key 87 | a[2] = path 88 | a[3] = json 89 | cmd := redis.NewStringCmd(ctx, a...) 90 | _ = tc(ctx, cmd) 91 | return cmd 92 | } 93 | 94 | func (tc tairCmdable) JsonSetArgs(ctx context.Context, key string, path string, json string, args *JsonSetArgs) *redis.StringCmd { 95 | a := make([]interface{}, 4) 96 | a[0] = "JSON.SET" 97 | a[1] = key 98 | a[2] = path 99 | a[3] = json 100 | a = append(a, args.GetArgs()...) 101 | cmd := redis.NewStringCmd(ctx, a...) 102 | _ = tc(ctx, cmd) 103 | return cmd 104 | } 105 | 106 | func (tc tairCmdable) JsonGet(ctx context.Context, key string) *redis.StringCmd { 107 | a := make([]interface{}, 2) 108 | a[0] = "JSON.GET" 109 | a[1] = key 110 | cmd := redis.NewStringCmd(ctx, a...) 111 | _ = tc(ctx, cmd) 112 | return cmd 113 | } 114 | 115 | func (tc tairCmdable) JsonGetPath(ctx context.Context, key string, path string) *redis.StringCmd { 116 | a := make([]interface{}, 3) 117 | a[0] = "JSON.GET" 118 | a[1] = key 119 | a[2] = path 120 | cmd := redis.NewStringCmd(ctx, a...) 121 | _ = tc(ctx, cmd) 122 | return cmd 123 | } 124 | 125 | func (tc tairCmdable) JsonGetArgs(ctx context.Context, key string, path string, args *JsonGetArgs) *redis.StringCmd { 126 | a := make([]interface{}, 3) 127 | a[0] = "JSON.GET" 128 | a[1] = key 129 | a[2] = path 130 | a = append(a, args.GetArgs()...) 131 | cmd := redis.NewStringCmd(ctx, a...) 132 | _ = tc(ctx, cmd) 133 | return cmd 134 | } 135 | 136 | func (tc tairCmdable) JsonDel(ctx context.Context, key string) *redis.IntCmd { 137 | a := make([]interface{}, 2) 138 | a[0] = "JSON.DEL" 139 | a[1] = key 140 | cmd := redis.NewIntCmd(ctx, a...) 141 | _ = tc(ctx, cmd) 142 | return cmd 143 | } 144 | 145 | func (tc tairCmdable) JsonDelPath(ctx context.Context, key string, path string) *redis.IntCmd { 146 | a := make([]interface{}, 3) 147 | a[0] = "JSON.DEL" 148 | a[1] = key 149 | a[2] = path 150 | cmd := redis.NewIntCmd(ctx, a...) 151 | _ = tc(ctx, cmd) 152 | return cmd 153 | } 154 | 155 | func (tc tairCmdable) JsonType(ctx context.Context, key string) *redis.StringCmd { 156 | a := make([]interface{}, 2) 157 | a[0] = "JSON.TYPE" 158 | a[1] = key 159 | cmd := redis.NewStringCmd(ctx, a...) 160 | _ = tc(ctx, cmd) 161 | return cmd 162 | } 163 | 164 | func (tc tairCmdable) JsonTypePath(ctx context.Context, key string, path string) *redis.StringCmd { 165 | a := make([]interface{}, 3) 166 | a[0] = "JSON.TYPE" 167 | a[1] = key 168 | a[2] = path 169 | cmd := redis.NewStringCmd(ctx, a...) 170 | _ = tc(ctx, cmd) 171 | return cmd 172 | } 173 | 174 | func (tc tairCmdable) JsonNumIncrBy(ctx context.Context, key string, value float64) *redis.FloatCmd { 175 | a := make([]interface{}, 3) 176 | a[0] = "JSON.NUMINCRBY" 177 | a[1] = key 178 | a[2] = value 179 | cmd := redis.NewFloatCmd(ctx, a...) 180 | _ = tc(ctx, cmd) 181 | return cmd 182 | } 183 | 184 | func (tc tairCmdable) JsonNumIncrByWithPath(ctx context.Context, key string, path string, value float64) *redis.FloatCmd { 185 | a := make([]interface{}, 4) 186 | a[0] = "JSON.NUMINCRBY" 187 | a[1] = key 188 | a[2] = path 189 | a[3] = value 190 | cmd := redis.NewFloatCmd(ctx, a...) 191 | _ = tc(ctx, cmd) 192 | return cmd 193 | } 194 | func (tc tairCmdable) JsonStrAppend(ctx context.Context, key string, json string) *redis.IntCmd { 195 | a := make([]interface{}, 3) 196 | a[0] = "JSON.STRAPPEND" 197 | a[1] = key 198 | a[2] = json 199 | cmd := redis.NewIntCmd(ctx, a...) 200 | _ = tc(ctx, cmd) 201 | return cmd 202 | } 203 | 204 | func (tc tairCmdable) JsonStrAppendWithPath(ctx context.Context, key string, path string, json string) *redis.IntCmd { 205 | a := make([]interface{}, 4) 206 | a[0] = "JSON.STRAPPEND" 207 | a[1] = key 208 | a[2] = path 209 | a[3] = json 210 | cmd := redis.NewIntCmd(ctx, a...) 211 | _ = tc(ctx, cmd) 212 | return cmd 213 | } 214 | func (tc tairCmdable) JsonStrLen(ctx context.Context, key string) *redis.IntCmd { 215 | a := make([]interface{}, 2) 216 | a[0] = "JSON.STRLEN" 217 | a[1] = key 218 | cmd := redis.NewIntCmd(ctx, a...) 219 | _ = tc(ctx, cmd) 220 | return cmd 221 | } 222 | 223 | func (tc tairCmdable) JsonStrLenWithPath(ctx context.Context, key string, path string) *redis.IntCmd { 224 | a := make([]interface{}, 3) 225 | a[0] = "JSON.STRLEN" 226 | a[1] = key 227 | a[2] = path 228 | cmd := redis.NewIntCmd(ctx, a...) 229 | _ = tc(ctx, cmd) 230 | return cmd 231 | } 232 | func (tc tairCmdable) JsonArrAppend(ctx context.Context, key string, jsons ...string) *redis.IntCmd { 233 | a := make([]interface{}, 2) 234 | a[0] = "JSON.ARRAPPEND" 235 | a[1] = key 236 | for _, json := range jsons { 237 | a = append(a, json) 238 | } 239 | cmd := redis.NewIntCmd(ctx, a...) 240 | _ = tc(ctx, cmd) 241 | return cmd 242 | } 243 | 244 | func (tc tairCmdable) JsonArrAppendWithPath(ctx context.Context, key string, path string, jsons ...string) *redis.IntCmd { 245 | a := make([]interface{}, 3) 246 | a[0] = "JSON.ARRAPPEND" 247 | a[1] = key 248 | a[2] = path 249 | for _, json := range jsons { 250 | a = append(a, json) 251 | } 252 | cmd := redis.NewIntCmd(ctx, a...) 253 | _ = tc(ctx, cmd) 254 | return cmd 255 | } 256 | func (tc tairCmdable) JsonArrPop(ctx context.Context, key string, path string) *redis.StringCmd { 257 | a := make([]interface{}, 3) 258 | a[0] = "JSON.ARRPOP" 259 | a[1] = key 260 | a[2] = path 261 | cmd := redis.NewStringCmd(ctx, a...) 262 | _ = tc(ctx, cmd) 263 | return cmd 264 | } 265 | 266 | func (tc tairCmdable) JsonArrPopWithPath(ctx context.Context, key string, path string, index int64) *redis.StringCmd { 267 | a := make([]interface{}, 4) 268 | a[0] = "JSON.ARRPOP" 269 | a[1] = key 270 | a[2] = path 271 | a[3] = index 272 | cmd := redis.NewStringCmd(ctx, a...) 273 | _ = tc(ctx, cmd) 274 | return cmd 275 | } 276 | 277 | func (tc tairCmdable) JsonArrInsert(ctx context.Context, args ...string) *redis.IntCmd { 278 | a := make([]interface{}, 1) 279 | a[0] = "JSON.ARRINSERT" 280 | for _, item := range args { 281 | a = append(a, item) 282 | } 283 | cmd := redis.NewIntCmd(ctx, a...) 284 | _ = tc(ctx, cmd) 285 | return cmd 286 | } 287 | 288 | func (tc tairCmdable) JsonArrLen(ctx context.Context, key string) *redis.IntCmd { 289 | a := make([]interface{}, 2) 290 | a[0] = "JSON.ARRLEN" 291 | a[1] = key 292 | cmd := redis.NewIntCmd(ctx, a...) 293 | _ = tc(ctx, cmd) 294 | return cmd 295 | } 296 | 297 | func (tc tairCmdable) JsonArrLenWithPath(ctx context.Context, key string, path string) *redis.IntCmd { 298 | a := make([]interface{}, 3) 299 | a[0] = "JSON.ARRLEN" 300 | a[1] = key 301 | a[2] = path 302 | cmd := redis.NewIntCmd(ctx, a...) 303 | _ = tc(ctx, cmd) 304 | return cmd 305 | } 306 | 307 | func (tc tairCmdable) JsonArrTrim(ctx context.Context, key string, path string, start int64, stop int64) *redis.IntCmd { 308 | a := make([]interface{}, 5) 309 | a[0] = "JSON.ARRTRIM" 310 | a[1] = key 311 | a[2] = path 312 | a[3] = start 313 | a[4] = stop 314 | cmd := redis.NewIntCmd(ctx, a...) 315 | _ = tc(ctx, cmd) 316 | return cmd 317 | } 318 | -------------------------------------------------------------------------------- /tair/tairgis.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | 7 | "github.com/redis/go-redis/v9" 8 | ) 9 | 10 | // args 11 | 12 | type GisArgs struct { 13 | arg 14 | radius string 15 | member string 16 | withoutWkt string 17 | withValue string 18 | withoutValue string 19 | withDist string 20 | asc string 21 | desc string 22 | count int 23 | } 24 | 25 | func (a GisArgs) NewGisArgs() *GisArgs { 26 | a.Set = make(map[string]bool) 27 | return &a 28 | } 29 | 30 | func (a *GisArgs) Radius() *GisArgs { 31 | a.Set[RADIUS] = true 32 | return a 33 | } 34 | 35 | func (a *GisArgs) Member() *GisArgs { 36 | a.Set[MEMBER] = true 37 | return a 38 | } 39 | 40 | func (a *GisArgs) WithoutWkt() *GisArgs { 41 | a.Set[WITHOUTWKT] = true 42 | return a 43 | } 44 | 45 | func (a *GisArgs) WithValue() *GisArgs { 46 | a.Set[WITHVALUE] = true 47 | return a 48 | } 49 | 50 | func (a *GisArgs) WithoutValue() *GisArgs { 51 | a.Set[WITHOUTVALUE] = true 52 | return a 53 | } 54 | 55 | func (a *GisArgs) WithDist() *GisArgs { 56 | a.Set[WITHDIST] = true 57 | return a 58 | } 59 | 60 | func (a *GisArgs) Asc() *GisArgs { 61 | a.Set[ASC] = true 62 | return a 63 | } 64 | 65 | func (a *GisArgs) Desc() *GisArgs { 66 | a.Set[DESC] = true 67 | return a 68 | } 69 | 70 | func (a *GisArgs) Count(count int) *GisArgs { 71 | a.Set[COUNT] = true 72 | a.count = count 73 | return a 74 | } 75 | 76 | func (a *GisArgs) GetArgs() []interface{} { 77 | args := make([]interface{}, 0) 78 | if _, ok := a.Set[RADIUS]; ok { 79 | args = append(args, RADIUS) 80 | } 81 | if _, ok := a.Set[MEMBER]; ok { 82 | args = append(args, MEMBER) 83 | } 84 | if _, ok := a.Set[WITHOUTWKT]; ok { 85 | args = append(args, WITHOUTWKT) 86 | } 87 | if _, ok := a.Set[WITHVALUE]; ok { 88 | args = append(args, WITHVALUE) 89 | } 90 | if _, ok := a.Set[WITHOUTVALUE]; ok { 91 | args = append(args, WITHOUTVALUE) 92 | } 93 | if _, ok := a.Set[WITHDIST]; ok { 94 | args = append(args, WITHDIST) 95 | } 96 | if _, ok := a.Set[ASC]; ok { 97 | args = append(args, ASC) 98 | } 99 | if _, ok := a.Set[DESC]; ok { 100 | args = append(args, DESC) 101 | } 102 | if _, ok := a.Set[COUNT]; ok { 103 | args = append(args, COUNT, a.count) 104 | } 105 | return args 106 | 107 | } 108 | 109 | // cmd 110 | 111 | type GisSearchResult struct { 112 | field []byte 113 | value []byte 114 | distance float64 115 | } 116 | 117 | func (cmd *GisSearchResult) Field() []byte { 118 | return cmd.field 119 | } 120 | 121 | func (cmd *GisSearchResult) Value() []byte { 122 | return cmd.value 123 | } 124 | 125 | func (cmd *GisSearchResult) FieldByString() string { 126 | if cmd.field == nil { 127 | return "" 128 | } 129 | return string(cmd.field[:]) 130 | } 131 | 132 | func (cmd *GisSearchResult) ValueByString() string { 133 | if cmd.value == nil { 134 | return "" 135 | } 136 | return string(cmd.value[:]) 137 | } 138 | 139 | func (cmd *GisSearchResult) Distance() float64 { 140 | return cmd.distance 141 | } 142 | 143 | //func (cmd *GisSearchResult) Build() *GisSearchResult { 144 | // panic("im") 145 | //} 146 | // 147 | //func (cmd *GisSearchResult) BuildFromVal(c interface{}) *GisSearchResult { 148 | // val := c.([]interface{}) 149 | // cmd.field = val[0].([]byte) 150 | // cmd.value = val[1].([]byte) 151 | // cmd.distance = val[2].(float64) 152 | // return cmd 153 | //} 154 | 155 | type GisSearchSliceMapCmd struct { 156 | *redis.SliceCmd 157 | *GisSearchResult 158 | } 159 | 160 | func NewGisSearchSliceMapCmd(sliceCmd *redis.SliceCmd) *GisSearchSliceMapCmd { 161 | cmd := &GisSearchSliceMapCmd{} 162 | cmd.SliceCmd = sliceCmd 163 | return cmd 164 | } 165 | 166 | func (cmd *GisSearchSliceMapCmd) Result() ([]*GisSearchResult, error) { 167 | val, err := cmd.SliceCmd.Result() 168 | if len(val) == 0 || err != nil { 169 | return make([]*GisSearchResult, 0), err 170 | } else { 171 | num := val[0].(int64) 172 | rawRes := val[1].([]interface{}) 173 | size := (len(rawRes)) / int(num) 174 | 175 | m := make([]*GisSearchResult, 0) 176 | for i := 0; i < int(num); i = i + 1 { 177 | result := &GisSearchResult{} 178 | result.field = []byte(rawRes[i*size].(string)) 179 | for j := i*size + 1; j < (i+1)*size; j++ { 180 | if floatVar, err := strconv.ParseFloat(rawRes[j].(string), 64); err == nil { 181 | result.distance = floatVar 182 | } else { 183 | result.value = []byte(rawRes[j].(string)) 184 | } 185 | } 186 | m = append(m, result) 187 | } 188 | return m, nil 189 | } 190 | } 191 | 192 | //func (cmd *GisSearchSliceMapCmd) String() string { 193 | // 194 | //} 195 | 196 | type GisStringStringMapCmd struct { 197 | *redis.SliceCmd 198 | } 199 | 200 | func NewGisStringStringMapCmd(sliceCmd *redis.SliceCmd) *GisStringStringMapCmd { 201 | cmd := &GisStringStringMapCmd{ 202 | SliceCmd: sliceCmd, 203 | } 204 | return cmd 205 | } 206 | 207 | func (cmd *GisStringStringMapCmd) Result() (map[string]string, error) { 208 | val, err := cmd.SliceCmd.Result() 209 | if len(val) == 0 || err != nil { 210 | return make(map[string]string, 0), err 211 | } else { 212 | rawRes := val[1].([]interface{}) 213 | m := make(map[string]string, 0) 214 | for i := 0; i < len(rawRes); i = i + 2 { 215 | m[rawRes[i].(string)] = rawRes[i+1].(string) 216 | } 217 | return m, nil 218 | } 219 | } 220 | 221 | type GisGetAllStringStringMapCmd struct { 222 | *redis.SliceCmd 223 | } 224 | 225 | func NewGisGetAllStringStringMapCmd(sliceCmd *redis.SliceCmd) *GisGetAllStringStringMapCmd { 226 | cmd := &GisGetAllStringStringMapCmd{ 227 | SliceCmd: sliceCmd, 228 | } 229 | return cmd 230 | } 231 | 232 | func (cmd *GisGetAllStringStringMapCmd) Result() (map[string]string, error) { 233 | val, err := cmd.SliceCmd.Result() 234 | if len(val) == 0 || err != nil { 235 | return make(map[string]string, 0), err 236 | } else { 237 | m := make(map[string]string, 0) 238 | for i := 0; i < len(val); i = i + 2 { 239 | m[val[i].(string)] = val[i+1].(string) 240 | } 241 | return m, nil 242 | } 243 | } 244 | 245 | type GisGetAllStringCmd struct { 246 | *redis.SliceCmd 247 | } 248 | 249 | func NewGisGetAllStringCmd(sliceCmd *redis.SliceCmd) *GisGetAllStringCmd { 250 | cmd := &GisGetAllStringCmd{ 251 | SliceCmd: sliceCmd, 252 | } 253 | return cmd 254 | } 255 | 256 | func (cmd *GisGetAllStringCmd) Result() ([]string, error) { 257 | val, err := cmd.SliceCmd.Result() 258 | if len(val) == 0 || err != nil { 259 | return make([]string, 0), err 260 | } else { 261 | m := make([]string, 0) 262 | for _, item := range val { 263 | if item == nil { 264 | m = append(m, "") 265 | } else { 266 | m = append(m, item.(string)) 267 | } 268 | } 269 | return m, nil 270 | } 271 | } 272 | 273 | type GisContainsStringCmd struct { 274 | *redis.SliceCmd 275 | } 276 | 277 | func NewGisContainsStringCmd(sliceCmd *redis.SliceCmd) *GisContainsStringCmd { 278 | cmd := &GisContainsStringCmd{ 279 | SliceCmd: sliceCmd, 280 | } 281 | return cmd 282 | } 283 | 284 | func (cmd *GisContainsStringCmd) Result() ([]string, error) { 285 | val, err := cmd.SliceCmd.Result() 286 | if len(val) == 0 || err != nil { 287 | return make([]string, 0), err 288 | } else { 289 | rawRes := val[1].([]interface{}) 290 | m := make([]string, 0) 291 | for _, item := range rawRes { 292 | if item == nil { 293 | m = append(m, "") 294 | } else { 295 | m = append(m, item.(string)) 296 | } 297 | } 298 | return m, nil 299 | } 300 | } 301 | 302 | func (tc tairCmdable) GisAdd(ctx context.Context, area string, polygonName string, polygonWktText string) *redis.IntCmd { 303 | args := make([]interface{}, 4) 304 | args[0] = "GIS.ADD" 305 | args[1] = area 306 | args[2] = polygonName 307 | args[3] = polygonWktText 308 | cmd := redis.NewIntCmd(ctx, args...) 309 | _ = tc(ctx, cmd) 310 | return cmd 311 | } 312 | 313 | func (tc tairCmdable) GisGet(ctx context.Context, area string, polygonName string) *redis.StringCmd { 314 | args := make([]interface{}, 3) 315 | args[0] = "GIS.GET" 316 | args[1] = area 317 | args[2] = polygonName 318 | cmd := redis.NewStringCmd(ctx, args...) 319 | _ = tc(ctx, cmd) 320 | return cmd 321 | } 322 | 323 | func (tc tairCmdable) GisSearch(ctx context.Context, area string, pointWktText string) *GisStringStringMapCmd { 324 | args := make([]interface{}, 3) 325 | args[0] = "GIS.SEARCH" 326 | args[1] = area 327 | args[2] = pointWktText 328 | cmd := redis.NewSliceCmd(ctx, args...) 329 | _ = tc(ctx, cmd) 330 | resCmd := NewGisStringStringMapCmd(cmd) 331 | return resCmd 332 | } 333 | 334 | func (tc tairCmdable) GisSearchArgs(ctx context.Context, area string, longitude, latitude, radius float64, 335 | geoUnit string, args *GisArgs) *GisSearchSliceMapCmd { 336 | a := make([]interface{}, 7) 337 | a[0] = "GIS.SEARCH" 338 | a[1] = area 339 | a[2] = RADIUS 340 | a[3] = longitude 341 | a[4] = latitude 342 | a[5] = radius 343 | a[6] = geoUnit 344 | a = append(a, args.GetArgs()...) 345 | cmd := redis.NewSliceCmd(ctx, a...) 346 | resCmd := NewGisSearchSliceMapCmd(cmd) 347 | _ = tc(ctx, cmd) 348 | return resCmd 349 | } 350 | 351 | func (tc tairCmdable) GisSearchArgsByMember(ctx context.Context, area string, member string, radius float64, geoUnit string, args *GisArgs) *GisSearchSliceMapCmd { 352 | a := make([]interface{}, 6) 353 | a[0] = "GIS.SEARCH" 354 | a[1] = area 355 | a[2] = MEMBER 356 | a[3] = member 357 | a[4] = radius 358 | a[5] = geoUnit 359 | a = append(a, args.GetArgs()...) 360 | cmd := redis.NewSliceCmd(ctx, a...) 361 | resCmd := NewGisSearchSliceMapCmd(cmd) 362 | _ = tc(ctx, cmd) 363 | return resCmd 364 | } 365 | 366 | func (tc tairCmdable) GisContains(ctx context.Context, area string, pointWktText string) *GisStringStringMapCmd { 367 | args := make([]interface{}, 3) 368 | args[0] = "GIS.CONTAINS" 369 | args[1] = area 370 | args[2] = pointWktText 371 | cmd := redis.NewSliceCmd(ctx, args...) 372 | resCmd := NewGisStringStringMapCmd(cmd) 373 | _ = tc(ctx, cmd) 374 | return resCmd 375 | } 376 | 377 | func (tc tairCmdable) GisContainsArgs(ctx context.Context, area string, pointWktText string, args *GisArgs) *GisContainsStringCmd { 378 | a := make([]interface{}, 3) 379 | a[0] = "GIS.CONTAINS" 380 | a[1] = area 381 | a[2] = pointWktText 382 | a = append(a, args.GetArgs()...) 383 | cmd := redis.NewSliceCmd(ctx, a...) 384 | _ = tc(ctx, cmd) 385 | resCmd := NewGisContainsStringCmd(cmd) 386 | return resCmd 387 | } 388 | 389 | func (tc tairCmdable) GisIntersects(ctx context.Context, area string, pointWktText string) *GisStringStringMapCmd { 390 | args := make([]interface{}, 3) 391 | args[0] = "GIS.INTERSECTS" 392 | args[1] = area 393 | args[2] = pointWktText 394 | cmd := redis.NewSliceCmd(ctx, args...) 395 | _ = tc(ctx, cmd) 396 | resCmd := NewGisStringStringMapCmd(cmd) 397 | return resCmd 398 | } 399 | 400 | func (tc tairCmdable) GisDel(ctx context.Context, area string, polygonName string) *redis.StringCmd { 401 | args := make([]interface{}, 3) 402 | args[0] = "GIS.DEL" 403 | args[1] = area 404 | args[2] = polygonName 405 | cmd := redis.NewStringCmd(ctx, args...) 406 | _ = tc(ctx, cmd) 407 | return cmd 408 | } 409 | 410 | func (tc tairCmdable) GisGetAll(ctx context.Context, area string) *GisGetAllStringStringMapCmd { 411 | args := make([]interface{}, 2) 412 | args[0] = "GIS.GETALL" 413 | args[1] = area 414 | cmd := redis.NewSliceCmd(ctx, args...) 415 | _ = tc(ctx, cmd) 416 | resCmd := NewGisGetAllStringStringMapCmd(cmd) 417 | return resCmd 418 | } 419 | 420 | func (tc tairCmdable) GisGetAllArgs(ctx context.Context, area string, args *GisArgs) *GisGetAllStringCmd { 421 | a := make([]interface{}, 2) 422 | a[0] = "GIS.GETALL" 423 | a[1] = area 424 | a = append(a, args.GetArgs()...) 425 | cmd := redis.NewSliceCmd(ctx, a...) 426 | _ = tc(ctx, cmd) 427 | resCmd := NewGisGetAllStringCmd(cmd) 428 | return resCmd 429 | } 430 | 431 | func (tc tairCmdable) GisWithin(ctx context.Context, area string, polygonWkt string, withoutWkt bool) *redis.MapStringStringCmd { 432 | a := make([]interface{}, 3) 433 | a[0] = "GIS.WITHIN" 434 | a[1] = area 435 | a[2] = polygonWkt 436 | if withoutWkt { 437 | a = append(a, "WITHOUTWKT") 438 | } 439 | cmd := redis.NewMapStringStringCmd(ctx, a...) 440 | _ = tc(ctx, cmd) 441 | return cmd 442 | } 443 | -------------------------------------------------------------------------------- /tair/tairhash.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/redis/go-redis/v9" 8 | ) 9 | 10 | type ExHSetArgs struct { 11 | arg 12 | 13 | xx string 14 | nx string 15 | ex time.Duration 16 | px time.Duration 17 | exAt time.Time 18 | pxAt time.Time 19 | ver int64 20 | abs int64 21 | } 22 | 23 | func (a ExHSetArgs) New() *ExHSetArgs { 24 | a.Set = make(map[string]bool) 25 | return &a 26 | } 27 | 28 | func (a *ExHSetArgs) Xx() *ExHSetArgs { 29 | a.Set[XX] = true 30 | return a 31 | } 32 | 33 | func (a *ExHSetArgs) Nx() *ExHSetArgs { 34 | a.Set[NX] = true 35 | return a 36 | } 37 | 38 | func (a *ExHSetArgs) Ex(ex time.Duration) *ExHSetArgs { 39 | a.Set[EX] = true 40 | a.ex = ex 41 | return a 42 | } 43 | 44 | func (a *ExHSetArgs) Px(px time.Duration) *ExHSetArgs { 45 | a.Set[PX] = true 46 | a.px = px 47 | return a 48 | } 49 | 50 | func (a *ExHSetArgs) ExAt(exAt time.Time) *ExHSetArgs { 51 | a.Set[EXAT] = true 52 | a.exAt = exAt 53 | return a 54 | } 55 | 56 | func (a *ExHSetArgs) PxAt(pxAt time.Time) *ExHSetArgs { 57 | a.Set[PXAT] = true 58 | a.pxAt = pxAt 59 | return a 60 | } 61 | 62 | func (a *ExHSetArgs) Ver(ver int64) *ExHSetArgs { 63 | a.Set[VER] = true 64 | a.ver = ver 65 | return a 66 | } 67 | 68 | func (a *ExHSetArgs) Abs(abs int64) *ExHSetArgs { 69 | a.Set[ABS] = true 70 | a.abs = abs 71 | return a 72 | } 73 | 74 | func (a *ExHSetArgs) GetArgs() []interface{} { 75 | args := make([]interface{}, 0) 76 | if _, ok := a.Set[XX]; ok { 77 | args = append(args, XX) 78 | } 79 | if _, ok := a.Set[NX]; ok { 80 | args = append(args, NX) 81 | } 82 | if _, ok := a.Set[EX]; ok { 83 | args = append(args, EX, toSec(a.ex)) 84 | } 85 | if _, ok := a.Set[PX]; ok { 86 | args = append(args, PX, toMs(a.px)) 87 | } 88 | if _, ok := a.Set[EXAT]; ok { 89 | args = append(args, EXAT, a.exAt.Unix()) 90 | } 91 | if _, ok := a.Set[PXAT]; ok { 92 | args = append(args, PXAT, a.pxAt.Unix()) 93 | } 94 | if _, ok := a.Set[VER]; ok { 95 | args = append(args, VER, a.ver) 96 | } 97 | if _, ok := a.Set[ABS]; ok { 98 | args = append(args, ABS, a.abs) 99 | } 100 | return args 101 | } 102 | 103 | type ExHMSetWithOptsArgs struct { 104 | arg 105 | 106 | field interface{} 107 | value interface{} 108 | ver int64 109 | exp int64 110 | } 111 | 112 | func (a ExHMSetWithOptsArgs) New() *ExHMSetWithOptsArgs { 113 | a.Set = make(map[string]bool) 114 | return &a 115 | } 116 | 117 | func (a *ExHMSetWithOptsArgs) Field(field interface{}) *ExHMSetWithOptsArgs { 118 | a.field = field 119 | return a 120 | } 121 | 122 | func (a *ExHMSetWithOptsArgs) Value(value interface{}) *ExHMSetWithOptsArgs { 123 | a.value = value 124 | return a 125 | } 126 | 127 | func (a *ExHMSetWithOptsArgs) SetVer(ver int64) *ExHMSetWithOptsArgs { 128 | a.ver = ver 129 | return a 130 | } 131 | 132 | func (a *ExHMSetWithOptsArgs) SetExp(exp int64) *ExHMSetWithOptsArgs { 133 | a.exp = exp 134 | return a 135 | } 136 | 137 | func (a *ExHMSetWithOptsArgs) GetArgs() []interface{} { 138 | args := make([]interface{}, 0) 139 | args = append(args, a.field) 140 | args = append(args, a.value) 141 | args = append(args, a.ver) 142 | args = append(args, a.exp) 143 | return args 144 | } 145 | 146 | type ExHIncrArgs struct { 147 | arg 148 | 149 | ex time.Duration 150 | px time.Duration 151 | exAt time.Time 152 | pxAt time.Time 153 | ver int64 154 | abs int64 155 | min int64 156 | max int64 157 | gt int64 158 | keepttl string 159 | } 160 | 161 | func (a ExHIncrArgs) New() *ExHIncrArgs { 162 | a.Set = make(map[string]bool) 163 | return &a 164 | } 165 | 166 | func (a *ExHIncrArgs) Ex(ex time.Duration) *ExHIncrArgs { 167 | a.Set[EX] = true 168 | a.ex = ex 169 | return a 170 | } 171 | 172 | func (a *ExHIncrArgs) Px(px time.Duration) *ExHIncrArgs { 173 | a.Set[PX] = true 174 | a.px = px 175 | return a 176 | } 177 | 178 | func (a *ExHIncrArgs) ExAt(exAt time.Time) *ExHIncrArgs { 179 | a.Set[EXAT] = true 180 | a.exAt = exAt 181 | return a 182 | } 183 | 184 | func (a *ExHIncrArgs) PxAt(pxAt time.Time) *ExHIncrArgs { 185 | a.Set[PXAT] = true 186 | a.pxAt = pxAt 187 | return a 188 | } 189 | 190 | func (a *ExHIncrArgs) Ver(ver int64) *ExHIncrArgs { 191 | a.Set[VER] = true 192 | a.ver = ver 193 | return a 194 | } 195 | 196 | func (a *ExHIncrArgs) Abs(ver int64) *ExHIncrArgs { 197 | a.Set[ABS] = true 198 | a.ver = ver 199 | return a 200 | } 201 | 202 | func (a *ExHIncrArgs) Min(min int64) *ExHIncrArgs { 203 | a.Set[MIN] = true 204 | a.min = min 205 | return a 206 | } 207 | 208 | func (a *ExHIncrArgs) Max(max int64) *ExHIncrArgs { 209 | a.Set[MAX] = true 210 | a.max = max 211 | return a 212 | } 213 | 214 | func (a *ExHIncrArgs) Gt(gt int64) *ExHIncrArgs { 215 | a.Set[GT] = true 216 | a.gt = gt 217 | return a 218 | } 219 | 220 | func (a *ExHIncrArgs) KeepTTL() *ExHIncrArgs { 221 | a.Set[KEEPTTL] = true 222 | return a 223 | } 224 | 225 | func (a ExHIncrArgs) GetArgs() []interface{} { 226 | args := make([]interface{}, 0) 227 | if _, ok := a.Set[EX]; ok { 228 | args = append(args, EX, toSec(a.ex)) 229 | } 230 | if _, ok := a.Set[PX]; ok { 231 | args = append(args, PX, toMs(a.px)) 232 | } 233 | if _, ok := a.Set[EXAT]; ok { 234 | args = append(args, EXAT, a.exAt.Unix()) 235 | } 236 | if _, ok := a.Set[PXAT]; ok { 237 | args = append(args, PXAT, a.pxAt.Unix()) 238 | } 239 | if _, ok := a.Set[VER]; ok { 240 | args = append(args, VER, a.ver) 241 | } 242 | if _, ok := a.Set[ABS]; ok { 243 | args = append(args, ABS, a.abs) 244 | } 245 | if _, ok := a.Set[MIN]; ok { 246 | args = append(args, MIN, a.min) 247 | } 248 | if _, ok := a.Set[MAX]; ok { 249 | args = append(args, MAX, a.max) 250 | } 251 | if _, ok := a.Set[GT]; ok { 252 | args = append(args, GT, a.gt) 253 | } 254 | if _, ok := a.Set[KEEPTTL]; ok { 255 | args = append(args, KEEPTTL) 256 | } 257 | return args 258 | } 259 | 260 | func (tc tairCmdable) ExHSet(ctx context.Context, key, field, value string) *redis.IntCmd { 261 | a := make([]interface{}, 4) 262 | a[0] = "exhset" 263 | a[1] = key 264 | a[2] = field 265 | a[3] = value 266 | cmd := redis.NewIntCmd(ctx, a...) 267 | _ = tc(ctx, cmd) 268 | return cmd 269 | } 270 | 271 | func (tc tairCmdable) ExHGet(ctx context.Context, key, field string) *redis.StringCmd { 272 | a := make([]interface{}, 3) 273 | a[0] = "exhget" 274 | a[1] = key 275 | a[2] = field 276 | cmd := redis.NewStringCmd(ctx, a...) 277 | _ = tc(ctx, cmd) 278 | return cmd 279 | } 280 | 281 | func (tc tairCmdable) ExHSetArgs(ctx context.Context, key, field, value string, arg *ExHSetArgs) *redis.IntCmd { 282 | a := make([]interface{}, 4) 283 | a[0] = "exhset" 284 | a[1] = key 285 | a[2] = field 286 | a[3] = value 287 | a = append(a, arg.GetArgs()...) 288 | cmd := redis.NewIntCmd(ctx, a...) 289 | _ = tc(ctx, cmd) 290 | return cmd 291 | } 292 | 293 | func (tc tairCmdable) ExHSetNx(ctx context.Context, key, field, value string) *redis.IntCmd { 294 | a := make([]interface{}, 4) 295 | a[0] = "exhsetnx" 296 | a[1] = key 297 | a[2] = field 298 | a[3] = value 299 | cmd := redis.NewIntCmd(ctx, a...) 300 | _ = tc(ctx, cmd) 301 | return cmd 302 | } 303 | 304 | func (tc tairCmdable) ExHMSet(ctx context.Context, key string, fieldValue map[string]string) *redis.StatusCmd { 305 | a := make([]interface{}, 2) 306 | a[0] = "exhmset" 307 | a[1] = key 308 | for k, v := range fieldValue { 309 | a = append(a, k) 310 | a = append(a, v) 311 | } 312 | cmd := redis.NewStatusCmd(ctx, a...) 313 | _ = tc(ctx, cmd) 314 | return cmd 315 | } 316 | 317 | func (tc tairCmdable) ExHMSetWithOpts(ctx context.Context, key string, arg ...*ExHMSetWithOptsArgs) *redis.StatusCmd { 318 | a := make([]interface{}, 2) 319 | a[0] = "exhmsetwithopts" 320 | a[1] = key 321 | for _, p := range arg { 322 | a = append(a, p.GetArgs()...) 323 | } 324 | cmd := redis.NewStatusCmd(ctx, a...) 325 | return cmd 326 | } 327 | 328 | func (tc tairCmdable) ExHPExpire(ctx context.Context, key, field string, milliseconds int) *redis.BoolCmd { 329 | a := make([]interface{}, 4) 330 | a[0] = "exhpexpire" 331 | a[1] = key 332 | a[2] = field 333 | a[3] = milliseconds 334 | cmd := redis.NewBoolCmd(ctx, a...) 335 | _ = tc(ctx, cmd) 336 | return cmd 337 | } 338 | 339 | func (tc tairCmdable) ExHPExpireAt(ctx context.Context, key, field string, milliUnixTime int) *redis.BoolCmd { 340 | a := make([]interface{}, 4) 341 | a[0] = "exhpexpireat" 342 | a[1] = key 343 | a[2] = field 344 | a[3] = milliUnixTime 345 | cmd := redis.NewBoolCmd(ctx, a...) 346 | _ = tc(ctx, cmd) 347 | return cmd 348 | } 349 | 350 | func (tc tairCmdable) ExHExpire(ctx context.Context, key, field string, seconds int) *redis.BoolCmd { 351 | a := make([]interface{}, 4) 352 | a[0] = "exhexpire" 353 | a[1] = key 354 | a[2] = field 355 | a[3] = seconds 356 | cmd := redis.NewBoolCmd(ctx, a...) 357 | _ = tc(ctx, cmd) 358 | return cmd 359 | } 360 | 361 | func (tc tairCmdable) ExHExpireAt(ctx context.Context, key, field string, unixTime int) *redis.BoolCmd { 362 | a := make([]interface{}, 4) 363 | a[0] = "exhexpireat" 364 | a[1] = key 365 | a[2] = field 366 | a[3] = unixTime 367 | cmd := redis.NewBoolCmd(ctx, a...) 368 | _ = tc(ctx, cmd) 369 | return cmd 370 | } 371 | 372 | func (tc tairCmdable) ExHPTTL(ctx context.Context, key, field string) *redis.IntCmd { 373 | a := make([]interface{}, 3) 374 | a[0] = "exhpttl" 375 | a[1] = key 376 | a[2] = field 377 | cmd := redis.NewIntCmd(ctx, a...) 378 | _ = tc(ctx, cmd) 379 | return cmd 380 | } 381 | 382 | func (tc tairCmdable) ExHTTL(ctx context.Context, key, field string) *redis.IntCmd { 383 | a := make([]interface{}, 3) 384 | a[0] = "exhttl" 385 | a[1] = key 386 | a[2] = field 387 | cmd := redis.NewIntCmd(ctx, a...) 388 | _ = tc(ctx, cmd) 389 | return cmd 390 | } 391 | 392 | func (tc tairCmdable) ExHVer(ctx context.Context, key, field string) *redis.IntCmd { 393 | a := make([]interface{}, 3) 394 | a[0] = "exhver" 395 | a[1] = key 396 | a[2] = field 397 | cmd := redis.NewIntCmd(ctx, a...) 398 | _ = tc(ctx, cmd) 399 | return cmd 400 | } 401 | 402 | func (tc tairCmdable) ExHSetVer(ctx context.Context, key, field string, version int) *redis.BoolCmd { 403 | a := make([]interface{}, 4) 404 | a[0] = "exhsetver" 405 | a[1] = key 406 | a[2] = field 407 | a[3] = version 408 | cmd := redis.NewBoolCmd(ctx, a...) 409 | _ = tc(ctx, cmd) 410 | return cmd 411 | } 412 | 413 | func (tc tairCmdable) ExHIncrBy(ctx context.Context, key, field string, value int) *redis.IntCmd { 414 | a := make([]interface{}, 4) 415 | a[0] = "exhincrby" 416 | a[1] = key 417 | a[2] = field 418 | a[3] = value 419 | cmd := redis.NewIntCmd(ctx, a...) 420 | _ = tc(ctx, cmd) 421 | return cmd 422 | } 423 | 424 | func (tc tairCmdable) ExHIncrByArgs(ctx context.Context, key, field string, value int, arg *ExHIncrArgs) *redis.IntCmd { 425 | a := make([]interface{}, 4) 426 | a[0] = "exhincrby" 427 | a[1] = key 428 | a[2] = field 429 | a[3] = value 430 | a = append(a, arg.GetArgs()...) 431 | cmd := redis.NewIntCmd(ctx, a...) 432 | _ = tc(ctx, cmd) 433 | return cmd 434 | } 435 | 436 | func (tc tairCmdable) ExHIncrByFloat(ctx context.Context, key, field string, value float64) *redis.StringCmd { 437 | a := make([]interface{}, 4) 438 | a[0] = "exhincrbyfloat" 439 | a[1] = key 440 | a[2] = field 441 | a[3] = value 442 | cmd := redis.NewStringCmd(ctx, a...) 443 | _ = tc(ctx, cmd) 444 | return cmd 445 | } 446 | 447 | func (tc tairCmdable) ExHIncrByFloatArgs(ctx context.Context, key, field string, value float64, arg *ExHIncrArgs) *redis.StringCmd { 448 | a := make([]interface{}, 4) 449 | a[0] = "exhincrbyfloat" 450 | a[1] = key 451 | a[2] = field 452 | a[3] = value 453 | a = append(a, arg.GetArgs()...) 454 | cmd := redis.NewStringCmd(ctx, a...) 455 | _ = tc(ctx, cmd) 456 | return cmd 457 | } 458 | 459 | func (tc tairCmdable) ExHGetWithVer(ctx context.Context, key, field string) *redis.SliceCmd { 460 | a := make([]interface{}, 3) 461 | a[0] = "exhgetwithver" 462 | a[1] = key 463 | a[2] = field 464 | cmd := redis.NewSliceCmd(ctx, a...) 465 | _ = tc(ctx, cmd) 466 | return cmd 467 | } 468 | 469 | func (tc tairCmdable) ExHMGet(ctx context.Context, key string, field ...string) *redis.StringSliceCmd { 470 | a := make([]interface{}, 2) 471 | a[0] = "exhmget" 472 | a[1] = key 473 | for _, f := range field { 474 | a = append(a, f) 475 | } 476 | cmd := redis.NewStringSliceCmd(ctx, a...) 477 | _ = tc(ctx, cmd) 478 | return cmd 479 | } 480 | 481 | func (tc tairCmdable) ExHMGetWithVer(ctx context.Context, key string, field ...string) *redis.SliceCmd { 482 | a := make([]interface{}, 2) 483 | a[0] = "exhmgetwithver" 484 | a[1] = key 485 | for _, f := range field { 486 | a = append(a, f) 487 | } 488 | cmd := redis.NewSliceCmd(ctx, a...) 489 | _ = tc(ctx, cmd) 490 | return cmd 491 | } 492 | 493 | func (tc tairCmdable) ExHDel(ctx context.Context, key string, field ...string) *redis.IntCmd { 494 | a := make([]interface{}, 2) 495 | a[0] = "exhdel" 496 | a[1] = key 497 | for _, f := range field { 498 | a = append(a, f) 499 | } 500 | cmd := redis.NewIntCmd(ctx, a...) 501 | _ = tc(ctx, cmd) 502 | return cmd 503 | } 504 | 505 | func (tc tairCmdable) ExHLen(ctx context.Context, key string) *redis.IntCmd { 506 | a := make([]interface{}, 2) 507 | a[0] = "exhlen" 508 | a[1] = key 509 | cmd := redis.NewIntCmd(ctx, a...) 510 | _ = tc(ctx, cmd) 511 | return cmd 512 | } 513 | 514 | func (tc tairCmdable) ExHExists(ctx context.Context, key, field string) *redis.BoolCmd { 515 | a := make([]interface{}, 3) 516 | a[0] = "exhexists" 517 | a[1] = key 518 | a[2] = field 519 | cmd := redis.NewBoolCmd(ctx, a...) 520 | _ = tc(ctx, cmd) 521 | return cmd 522 | } 523 | 524 | func (tc tairCmdable) ExHStrLen(ctx context.Context, key, field string) *redis.IntCmd { 525 | a := make([]interface{}, 3) 526 | a[0] = "exhstrlen" 527 | a[1] = key 528 | a[2] = field 529 | cmd := redis.NewIntCmd(ctx, a...) 530 | _ = tc(ctx, cmd) 531 | return cmd 532 | } 533 | 534 | func (tc tairCmdable) ExHKeys(ctx context.Context, key string) *redis.StringSliceCmd { 535 | a := make([]interface{}, 2) 536 | a[0] = "exhkeys" 537 | a[1] = key 538 | cmd := redis.NewStringSliceCmd(ctx, a...) 539 | _ = tc(ctx, cmd) 540 | return cmd 541 | } 542 | 543 | func (tc tairCmdable) ExHVals(ctx context.Context, key string) *redis.StringSliceCmd { 544 | a := make([]interface{}, 2) 545 | a[0] = "exhvals" 546 | a[1] = key 547 | cmd := redis.NewStringSliceCmd(ctx, a...) 548 | _ = tc(ctx, cmd) 549 | return cmd 550 | } 551 | 552 | func (tc tairCmdable) ExHGetAll(ctx context.Context, key string) *redis.MapStringStringCmd { 553 | a := make([]interface{}, 2) 554 | a[0] = "exhgetall" 555 | a[1] = key 556 | cmd := redis.NewMapStringStringCmd(ctx, a...) 557 | _ = tc(ctx, cmd) 558 | return cmd 559 | } 560 | 561 | func (tc tairCmdable) ExHScan(ctx context.Context, key string, cursor string) *redis.SliceCmd { 562 | a := make([]interface{}, 3) 563 | a[0] = "exhscan" 564 | a[1] = key 565 | a[2] = cursor 566 | cmd := redis.NewSliceCmd(ctx, a...) 567 | _ = tc(ctx, cmd) 568 | return cmd 569 | } 570 | -------------------------------------------------------------------------------- /tair/tairhash_test.go: -------------------------------------------------------------------------------- 1 | package tair_test 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | "time" 8 | 9 | "github.com/alibaba/tair-go/tair" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/suite" 12 | ) 13 | 14 | type TairHashTestSuite struct { 15 | suite.Suite 16 | tairClient *tair.TairClient 17 | } 18 | 19 | func (suite *TairHashTestSuite) SetupTest() { 20 | suite.tairClient = tair.NewTairClient(redisOptions()) 21 | assert.Equal(suite.T(), "OK", suite.tairClient.FlushDB(ctx).Val()) 22 | } 23 | 24 | func (suite *TairHashTestSuite) TearDownTest() { 25 | assert.NoError(suite.T(), suite.tairClient.Close()) 26 | } 27 | 28 | func (suite *TairHashTestSuite) TestExHSet() { 29 | res, err := suite.tairClient.ExHSet(ctx, "k1", "f1", "v1").Result() 30 | assert.NoError(suite.T(), err) 31 | assert.Equal(suite.T(), res, int64(1)) 32 | 33 | res, err = suite.tairClient.Exists(ctx, "k1").Result() 34 | assert.NoError(suite.T(), err) 35 | assert.Equal(suite.T(), res, int64(1)) 36 | 37 | result, err := suite.tairClient.ExHGet(ctx, "k1", "f1").Result() 38 | assert.NoError(suite.T(), err) 39 | assert.Equal(suite.T(), result, "v1") 40 | } 41 | 42 | func (suite *TairHashTestSuite) TestExHset() { 43 | res, err := suite.tairClient.ExHSet(ctx, "k1", "f1", "v1").Result() 44 | assert.NoError(suite.T(), err) 45 | assert.Equal(suite.T(), res, int64(1)) 46 | 47 | res, err = suite.tairClient.Exists(ctx, "k1").Result() 48 | assert.NoError(suite.T(), err) 49 | assert.Equal(suite.T(), res, int64(1)) 50 | 51 | result, err := suite.tairClient.ExHGet(ctx, "k1", "f1").Result() 52 | assert.NoError(suite.T(), err) 53 | assert.Equal(suite.T(), result, "v1") 54 | } 55 | 56 | func (suite *TairHashTestSuite) TestExHSetByArgs() { 57 | a := tair.ExHSetArgs{}.New() 58 | a.Set = make(map[string]bool) 59 | a.Xx() 60 | res, err := suite.tairClient.ExHSetArgs(ctx, "k1", "f1", "v1", a).Result() 61 | assert.NoError(suite.T(), err) 62 | assert.Equal(suite.T(), res, int64(-1)) 63 | 64 | res, err = suite.tairClient.Exists(ctx, "k1").Result() 65 | assert.NoError(suite.T(), err) 66 | assert.Equal(suite.T(), res, int64(0)) 67 | } 68 | 69 | func (suite *TairHashTestSuite) TestExHSetNx() { 70 | res, err := suite.tairClient.ExHSetNx(ctx, "k1", "f1", "v1").Result() 71 | assert.NoError(suite.T(), err) 72 | assert.Equal(suite.T(), res, int64(1)) 73 | 74 | res, err = suite.tairClient.Exists(ctx, "k1").Result() 75 | assert.NoError(suite.T(), err) 76 | assert.Equal(suite.T(), res, int64(1)) 77 | } 78 | 79 | func (suite *TairHashTestSuite) TestExHMget() { 80 | a := make(map[string]string) 81 | a["f1"] = "v1" 82 | a["f2"] = "v2" 83 | a["f3"] = "v3" 84 | res2, err2 := suite.tairClient.ExHMSet(ctx, "k1", a).Result() 85 | assert.NoError(suite.T(), err2) 86 | assert.Equal(suite.T(), res2, "OK") 87 | 88 | result, err := suite.tairClient.ExHMGet(ctx, "k1", "f1", "f2", "f3").Result() 89 | assert.NoError(suite.T(), err) 90 | assert.Equal(suite.T(), result[0], "v1") 91 | assert.Equal(suite.T(), result[1], "v2") 92 | assert.Equal(suite.T(), result[2], "v3") 93 | } 94 | 95 | func (suite *TairHashTestSuite) TestExHMSetWithOpts() { 96 | b := tair.ExHMSetWithOptsArgs{}.New() 97 | b.Field("f1") 98 | b.Value("v1") 99 | b.SetExp(5) 100 | b.SetVer(99) 101 | _, err := suite.tairClient.ExHMSetWithOpts(ctx, "k1", b).Result() 102 | assert.NoError(suite.T(), err) 103 | } 104 | 105 | func (suite *TairHashTestSuite) TestExHPExpire() { 106 | res, err := suite.tairClient.ExHSet(ctx, "k1", "f1", "v1").Result() 107 | assert.NoError(suite.T(), err) 108 | assert.Equal(suite.T(), res, int64(1)) 109 | suite.tairClient.ExHExpire(ctx, "k1", "f1", 1) 110 | time.Sleep(time.Duration(2) * time.Second) 111 | 112 | res, err1 := suite.tairClient.Exists(ctx, "k1").Result() 113 | assert.NoError(suite.T(), err1) 114 | assert.Equal(suite.T(), res, int64(0)) 115 | } 116 | 117 | func (suite *TairHashTestSuite) TestExHSetVer() { 118 | res, err := suite.tairClient.ExHSet(ctx, "k1", "f1", "v1").Result() 119 | assert.NoError(suite.T(), err) 120 | assert.Equal(suite.T(), res, int64(1)) 121 | 122 | res1, err := suite.tairClient.ExHSetVer(ctx, "k1", "f1", 10).Result() 123 | assert.NoError(suite.T(), err) 124 | assert.Equal(suite.T(), res1, true) 125 | 126 | res, err = suite.tairClient.ExHVer(ctx, "k1", "f1").Result() 127 | assert.NoError(suite.T(), err) 128 | assert.Equal(suite.T(), res, int64(10)) 129 | } 130 | 131 | func (suite *TairHashTestSuite) TestExHIncrBy() { 132 | res, err := suite.tairClient.ExHIncrBy(ctx, "k1", "f1", 1).Result() 133 | assert.NoError(suite.T(), err) 134 | assert.Equal(suite.T(), res, int64(1)) 135 | 136 | res, err = suite.tairClient.ExHIncrBy(ctx, "k1", "f1", -1).Result() 137 | assert.NoError(suite.T(), err) 138 | assert.Equal(suite.T(), res, int64(0)) 139 | 140 | res, err = suite.tairClient.ExHIncrBy(ctx, "k1", "f1", -10).Result() 141 | assert.NoError(suite.T(), err) 142 | assert.Equal(suite.T(), res, int64(-10)) 143 | } 144 | 145 | func (suite *TairHashTestSuite) TestExHIncrByArgs() { 146 | //_, err := suite.tairClient.ExHIncrByArgs(ctx, "k1", "f1", 11, tair.ExHIncrArgs{}.New().Min(0).Max(10)).Result() 147 | //assert.Equal(suite.T(), err.Error(), ) 148 | } 149 | 150 | func (suite *TairHashTestSuite) TestExHIncrByFloat() { 151 | res, err := suite.tairClient.ExHIncrByFloat(ctx, "k1", "f1", 1.5).Result() 152 | assert.NoError(suite.T(), err) 153 | f, _ := strconv.ParseFloat(res, 64) 154 | assert.InDelta(suite.T(), f, 1.5, 0.01) 155 | 156 | res, err = suite.tairClient.ExHIncrByFloat(ctx, "k1", "f1", -1.5).Result() 157 | assert.NoError(suite.T(), err) 158 | assert.Equal(suite.T(), res, "0") 159 | 160 | res, err = suite.tairClient.ExHIncrByFloat(ctx, "k1", "f1", -10.7).Result() 161 | assert.NoError(suite.T(), err) 162 | f, _ = strconv.ParseFloat(res, 64) 163 | assert.InDelta(suite.T(), f, -10.7, 0.01) 164 | } 165 | 166 | func (suite *TairHashTestSuite) TestExHIncrByFloatExpire() { 167 | res, err := suite.tairClient.ExHIncrByFloatArgs(ctx, "k1", "f1", 5.1, tair.ExHIncrArgs{}.New().Ex(1)).Result() 168 | assert.NoError(suite.T(), err) 169 | f, _ := strconv.ParseFloat(res, 64) 170 | assert.InDelta(suite.T(), f, 5.1, 0.01) 171 | 172 | time.Sleep(time.Duration(2) * time.Second) 173 | 174 | res2, err := suite.tairClient.ExHLen(ctx, "k1").Result() 175 | assert.NoError(suite.T(), err) 176 | assert.Equal(suite.T(), res2, int64(0)) 177 | } 178 | 179 | func (suite *TairHashTestSuite) TestExHGetWithVer() { 180 | res, err := suite.tairClient.ExHSet(ctx, "k1", "f1", "v1").Result() 181 | assert.NoError(suite.T(), err) 182 | assert.Equal(suite.T(), res, int64(1)) 183 | 184 | res2, err := suite.tairClient.ExHGetWithVer(ctx, "k1", "f1").Result() 185 | assert.NoError(suite.T(), err) 186 | assert.Equal(suite.T(), res2[0], "v1") 187 | assert.Equal(suite.T(), res2[1], int64(1)) 188 | } 189 | 190 | func (suite *TairHashTestSuite) TestExHMGetWithVer() { 191 | a := make(map[string]string) 192 | a["f1"] = "v1" 193 | a["f2"] = "v2" 194 | a["f3"] = "v3" 195 | res2, err2 := suite.tairClient.ExHMSet(ctx, "k1", a).Result() 196 | assert.NoError(suite.T(), err2) 197 | assert.Equal(suite.T(), res2, "OK") 198 | 199 | result, err := suite.tairClient.ExHMGetWithVer(ctx, "k1", "f1", "f2", "f3").Result() 200 | assert.NoError(suite.T(), err) 201 | v1 := make([]interface{}, 0) 202 | v1 = append(v1, "v1", int64(1)) 203 | assert.Equal(suite.T(), result[0], v1) 204 | } 205 | 206 | func (suite *TairHashTestSuite) TestExHDelExHLen() { 207 | a := make(map[string]string) 208 | a["f1"] = "v1" 209 | a["f2"] = "v2" 210 | a["f3"] = "v3" 211 | res2, err2 := suite.tairClient.ExHMSet(ctx, "k1", a).Result() 212 | assert.NoError(suite.T(), err2) 213 | assert.Equal(suite.T(), res2, "OK") 214 | 215 | res, err := suite.tairClient.ExHDel(ctx, "k1", "not-exists").Result() 216 | assert.NoError(suite.T(), err) 217 | assert.Equal(suite.T(), res, int64(0)) 218 | 219 | res, err = suite.tairClient.ExHLen(ctx, "k1").Result() 220 | assert.NoError(suite.T(), err) 221 | assert.Equal(suite.T(), res, int64(3)) 222 | 223 | res, err = suite.tairClient.ExHDel(ctx, "k1", "f1").Result() 224 | assert.NoError(suite.T(), err) 225 | assert.Equal(suite.T(), res, int64(1)) 226 | 227 | res3, err := suite.tairClient.ExHExists(ctx, "k1", "f1").Result() 228 | assert.NoError(suite.T(), err) 229 | assert.Equal(suite.T(), res3, false) 230 | 231 | res, err = suite.tairClient.ExHLen(ctx, "k1").Result() 232 | assert.NoError(suite.T(), err) 233 | assert.Equal(suite.T(), res, int64(2)) 234 | 235 | res, err = suite.tairClient.ExHStrLen(ctx, "k1", "f2").Result() 236 | assert.NoError(suite.T(), err) 237 | assert.Equal(suite.T(), res, int64(2)) 238 | } 239 | 240 | func (suite *TairHashTestSuite) TestExHKeysExHVals() { 241 | a := make(map[string]string) 242 | a["f1"] = "v1" 243 | a["f2"] = "v2" 244 | a["f3"] = "v3" 245 | res2, err2 := suite.tairClient.ExHMSet(ctx, "k1", a).Result() 246 | assert.NoError(suite.T(), err2) 247 | assert.Equal(suite.T(), res2, "OK") 248 | 249 | res, err := suite.tairClient.ExHKeys(ctx, "k1").Result() 250 | assert.NoError(suite.T(), err) 251 | sort.Strings(res) 252 | assert.Equal(suite.T(), res, []string{"f1", "f2", "f3"}) 253 | 254 | res, err = suite.tairClient.ExHVals(ctx, "k1").Result() 255 | assert.NoError(suite.T(), err) 256 | sort.Strings(res) 257 | assert.Equal(suite.T(), res, []string{"v1", "v2", "v3"}) 258 | } 259 | 260 | func (suite *TairHashTestSuite) TestExHGetAll() { 261 | a := make(map[string]string) 262 | a["f1"] = "v1" 263 | a["f2"] = "v2" 264 | a["f3"] = "v3" 265 | res2, err2 := suite.tairClient.ExHMSet(ctx, "k1", a).Result() 266 | assert.NoError(suite.T(), err2) 267 | assert.Equal(suite.T(), res2, "OK") 268 | 269 | res, err := suite.tairClient.ExHGetAll(ctx, "k1").Result() 270 | assert.NoError(suite.T(), err) 271 | assert.Equal(suite.T(), res, map[string]string{"f1": "v1", "f2": "v2", "f3": "v3"}) 272 | } 273 | 274 | func TestTairHashTestSuite(t *testing.T) { 275 | suite.Run(t, new(TairHashTestSuite)) 276 | } 277 | -------------------------------------------------------------------------------- /tair/tairpipeline.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/redis/go-redis/v9" 7 | ) 8 | 9 | type TairPipeline struct { 10 | *redis.Pipeline // receive pipeline method to handle multi cmd 11 | tairCmdable // receive tair module command 12 | } 13 | 14 | func (p *TairPipeline) init() { 15 | p.tairCmdable = p.Process 16 | } 17 | 18 | func (t *TairClient) Process(ctx context.Context, cmd redis.Cmder) error { 19 | return t.Client.Process(ctx, cmd) 20 | } 21 | -------------------------------------------------------------------------------- /tair/tairpipeline_test.go: -------------------------------------------------------------------------------- 1 | package tair_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/alibaba/tair-go/tair" 8 | "github.com/redis/go-redis/v9" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/suite" 11 | ) 12 | 13 | type PipelineTestSuite struct { 14 | suite.Suite 15 | tairClient *tair.TairClient 16 | tairClusterClient *tair.TairClusterClient 17 | } 18 | 19 | func (suite *PipelineTestSuite) SetupTest() { 20 | suite.tairClient = tair.NewTairClient(redisOptions()) 21 | assert.Equal(suite.T(), "OK", suite.tairClient.FlushDB(ctx).Val()) 22 | 23 | suite.tairClusterClient = cluster.newClusterClient(ctx, redisClusterOptions()) 24 | err := suite.tairClusterClient.ForEachMaster(ctx, func(ctx context.Context, master *redis.Client) error { 25 | return master.FlushDB(ctx).Err() 26 | }) 27 | assert.NoError(suite.T(), err) 28 | } 29 | 30 | func (suite *PipelineTestSuite) TestTairPipeline() { 31 | pipe := suite.tairClient.TairPipeline() 32 | pipe.Set(ctx, "key", "value", 0) 33 | pipe.Get(ctx, "key") 34 | cmds, err := pipe.Exec(ctx) 35 | assert.NoError(suite.T(), err) 36 | assert.Equal(suite.T(), "value", cmds[1].(*redis.StringCmd).Val()) 37 | } 38 | 39 | func (suite *PipelineTestSuite) TestTairPipelined() { 40 | cmds, err := suite.tairClient.TairPipelined(ctx, func(p redis.Pipeliner) error { 41 | p.Set(ctx, "key", "value", 0) 42 | p.Get(ctx, "key") 43 | return nil 44 | }) 45 | assert.NoError(suite.T(), err) 46 | assert.Equal(suite.T(), "value", cmds[1].(*redis.StringCmd).Val()) 47 | } 48 | 49 | func (suite *PipelineTestSuite) TestTairClusterPipeline() { 50 | pipe := suite.tairClusterClient.TairPipeline() 51 | pipe.Set(ctx, "key", "value", 0) 52 | pipe.Get(ctx, "key") 53 | cmds, err := pipe.Exec(ctx) 54 | assert.NoError(suite.T(), err) 55 | assert.Equal(suite.T(), "value", cmds[1].(*redis.StringCmd).Val()) 56 | } 57 | 58 | func (suite *PipelineTestSuite) TestTairClusterPipelined() { 59 | cmds, err := suite.tairClusterClient.TairPipelined(ctx, func(p redis.Pipeliner) error { 60 | p.Set(ctx, "key", "value", 0) 61 | p.Get(ctx, "key") 62 | return nil 63 | }) 64 | assert.NoError(suite.T(), err) 65 | assert.Equal(suite.T(), "value", cmds[1].(*redis.StringCmd).Val()) 66 | } 67 | 68 | func TestTairPipelineTestSuite(t *testing.T) { 69 | suite.Run(t, new(PipelineTestSuite)) 70 | } 71 | -------------------------------------------------------------------------------- /tair/tairroaring.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/redis/go-redis/v9" 7 | ) 8 | 9 | func (tc tairCmdable) TrSetBit(ctx context.Context, key string, offset int64, value int64) *redis.IntCmd { 10 | a := make([]interface{}, 4) 11 | a[0] = "tr.setbit" 12 | a[1] = key 13 | a[2] = offset 14 | a[3] = value 15 | cmd := redis.NewIntCmd(ctx, a...) 16 | _ = tc(ctx, cmd) 17 | return cmd 18 | } 19 | 20 | func (tc tairCmdable) TrSetBits(ctx context.Context, key string, fields ...int64) *redis.IntCmd { 21 | a := make([]interface{}, 2) 22 | a[0] = "tr.setbits" 23 | a[1] = key 24 | for _, f := range fields { 25 | a = append(a, f) 26 | } 27 | cmd := redis.NewIntCmd(ctx, a...) 28 | _ = tc(ctx, cmd) 29 | return cmd 30 | } 31 | func (tc tairCmdable) TrGetBit(ctx context.Context, key string, offset int64) *redis.IntCmd { 32 | a := make([]interface{}, 3) 33 | a[0] = "tr.getbit" 34 | a[1] = key 35 | a[2] = offset 36 | cmd := redis.NewIntCmd(ctx, a...) 37 | _ = tc(ctx, cmd) 38 | return cmd 39 | } 40 | 41 | func (tc tairCmdable) TrGetBits(ctx context.Context, key string, fields ...int64) *redis.IntSliceCmd { 42 | a := make([]interface{}, 2) 43 | a[0] = "tr.getbits" 44 | a[1] = key 45 | for _, f := range fields { 46 | a = append(a, f) 47 | } 48 | cmd := redis.NewIntSliceCmd(ctx, a...) 49 | _ = tc(ctx, cmd) 50 | return cmd 51 | } 52 | 53 | func (tc tairCmdable) TrClearBits(ctx context.Context, key string, fields ...int64) *redis.IntCmd { 54 | a := make([]interface{}, 2) 55 | a[0] = "tr.clearbits" 56 | a[1] = key 57 | for _, f := range fields { 58 | a = append(a, f) 59 | } 60 | cmd := redis.NewIntCmd(ctx, a...) 61 | _ = tc(ctx, cmd) 62 | return cmd 63 | } 64 | 65 | func (tc tairCmdable) TrRange(ctx context.Context, key string, start int64, end int64) *redis.IntSliceCmd { 66 | a := make([]interface{}, 4) 67 | a[0] = "tr.range" 68 | a[1] = key 69 | a[2] = start 70 | a[3] = end 71 | cmd := redis.NewIntSliceCmd(ctx, a...) 72 | _ = tc(ctx, cmd) 73 | return cmd 74 | } 75 | 76 | func (tc tairCmdable) TrRangeBitArray(ctx context.Context, key string, start int64, end int64) *redis.StringCmd { 77 | a := make([]interface{}, 4) 78 | a[0] = "tr.rangebitarray" 79 | a[1] = key 80 | a[2] = start 81 | a[3] = end 82 | cmd := redis.NewStringCmd(ctx, a...) 83 | _ = tc(ctx, cmd) 84 | return cmd 85 | } 86 | 87 | func (tc tairCmdable) TrAppendBitArray(ctx context.Context, key string, offset int64, value string) *redis.IntCmd { 88 | a := make([]interface{}, 4) 89 | a[0] = "tr.appendbitarray" 90 | a[1] = key 91 | a[2] = offset 92 | a[3] = value 93 | cmd := redis.NewIntCmd(ctx, a...) 94 | _ = tc(ctx, cmd) 95 | return cmd 96 | } 97 | 98 | func (tc tairCmdable) TrSetRange(ctx context.Context, key string, start int64, end int64) *redis.IntCmd { 99 | a := make([]interface{}, 4) 100 | a[0] = "tr.setrange" 101 | a[1] = key 102 | a[2] = start 103 | a[3] = end 104 | cmd := redis.NewIntCmd(ctx, a...) 105 | _ = tc(ctx, cmd) 106 | return cmd 107 | } 108 | 109 | func (tc tairCmdable) TrFlipRange(ctx context.Context, key string, start int64, end string) *redis.IntCmd { 110 | a := make([]interface{}, 4) 111 | a[0] = "tr.fliprange" 112 | a[1] = key 113 | a[2] = start 114 | a[3] = end 115 | cmd := redis.NewIntCmd(ctx, a...) 116 | _ = tc(ctx, cmd) 117 | return cmd 118 | } 119 | 120 | func (tc tairCmdable) TrBitCount(ctx context.Context, key string) *redis.IntCmd { 121 | a := make([]interface{}, 2) 122 | a[0] = "tr.bitcount" 123 | a[1] = key 124 | cmd := redis.NewIntCmd(ctx, a...) 125 | _ = tc(ctx, cmd) 126 | return cmd 127 | } 128 | 129 | func (tc tairCmdable) TrBitCountRange(ctx context.Context, key string, start int64, end int64) *redis.IntCmd { 130 | a := make([]interface{}, 4) 131 | a[0] = "tr.bitcount" 132 | a[1] = key 133 | a[2] = start 134 | a[3] = end 135 | cmd := redis.NewIntCmd(ctx, a...) 136 | _ = tc(ctx, cmd) 137 | return cmd 138 | } 139 | 140 | func (tc tairCmdable) TrMin(ctx context.Context, key string) *redis.IntCmd { 141 | a := make([]interface{}, 2) 142 | a[0] = "tr.min" 143 | a[1] = key 144 | cmd := redis.NewIntCmd(ctx, a...) 145 | _ = tc(ctx, cmd) 146 | return cmd 147 | } 148 | 149 | func (tc tairCmdable) TrMax(ctx context.Context, key string) *redis.IntCmd { 150 | a := make([]interface{}, 2) 151 | a[0] = "tr.max" 152 | a[1] = key 153 | cmd := redis.NewIntCmd(ctx, a...) 154 | _ = tc(ctx, cmd) 155 | return cmd 156 | } 157 | 158 | func (tc tairCmdable) TrOptimize(ctx context.Context, key string) *redis.StringCmd { 159 | a := make([]interface{}, 2) 160 | a[0] = "tr.optimize" 161 | a[1] = key 162 | cmd := redis.NewStringCmd(ctx, a...) 163 | _ = tc(ctx, cmd) 164 | return cmd 165 | } 166 | 167 | func (tc tairCmdable) TrStat(ctx context.Context, key string, json bool) *redis.StringCmd { 168 | a := make([]interface{}, 2) 169 | a[0] = "tr.stat" 170 | a[1] = key 171 | if json { 172 | a = append(a, "JSON") 173 | } 174 | cmd := redis.NewStringCmd(ctx, a...) 175 | _ = tc(ctx, cmd) 176 | return cmd 177 | } 178 | func (tc tairCmdable) TrBitPosCount(ctx context.Context, key string, value int64, count int64) *redis.IntCmd { 179 | a := make([]interface{}, 4) 180 | a[0] = "tr.bitpos" 181 | a[1] = key 182 | a[2] = value 183 | a[3] = count 184 | cmd := redis.NewIntCmd(ctx, a...) 185 | _ = tc(ctx, cmd) 186 | return cmd 187 | } 188 | 189 | func (tc tairCmdable) TrBitPos(ctx context.Context, key string, value int64) *redis.IntCmd { 190 | a := make([]interface{}, 3) 191 | a[0] = "tr.bitpos" 192 | a[1] = key 193 | a[2] = value 194 | cmd := redis.NewIntCmd(ctx, a...) 195 | _ = tc(ctx, cmd) 196 | return cmd 197 | } 198 | 199 | func (tc tairCmdable) TrRank(ctx context.Context, key string, offset int64) *redis.IntCmd { 200 | a := make([]interface{}, 3) 201 | a[0] = "tr.rank" 202 | a[1] = key 203 | a[2] = offset 204 | cmd := redis.NewIntCmd(ctx, a...) 205 | _ = tc(ctx, cmd) 206 | return cmd 207 | } 208 | 209 | func (tc tairCmdable) TrBitOp(ctx context.Context, destKey string, operation string, keys ...string) *redis.IntCmd { 210 | a := make([]interface{}, 3) 211 | a[0] = "tr.bitop" 212 | a[1] = destKey 213 | a[2] = operation 214 | for _, k := range keys { 215 | a = append(a, k) 216 | } 217 | cmd := redis.NewIntCmd(ctx, a...) 218 | _ = tc(ctx, cmd) 219 | return cmd 220 | } 221 | 222 | func (tc tairCmdable) TrBitOpCard(ctx context.Context, operation string, keys ...string) *redis.IntCmd { 223 | a := make([]interface{}, 2) 224 | a[0] = "tr.bitopcard" 225 | a[1] = operation 226 | for _, k := range keys { 227 | a = append(a, k) 228 | } 229 | cmd := redis.NewIntCmd(ctx, a...) 230 | _ = tc(ctx, cmd) 231 | return cmd 232 | } 233 | 234 | func (tc tairCmdable) TrScanCount(ctx context.Context, key string, cursor int64, count int64) *redis.SliceCmd { 235 | a := make([]interface{}, 5) 236 | a[0] = "tr.scan" 237 | a[1] = key 238 | a[2] = cursor 239 | a[3] = "COUNT" 240 | a[4] = count 241 | cmd := redis.NewSliceCmd(ctx, a...) 242 | _ = tc(ctx, cmd) 243 | return cmd 244 | } 245 | 246 | func (tc tairCmdable) TrScan(ctx context.Context, key string, cursor int64) *redis.SliceCmd { 247 | a := make([]interface{}, 3) 248 | a[0] = "tr.scan" 249 | a[1] = key 250 | a[2] = cursor 251 | cmd := redis.NewSliceCmd(ctx, a...) 252 | _ = tc(ctx, cmd) 253 | return cmd 254 | } 255 | 256 | func (tc tairCmdable) TrDiff(ctx context.Context, destKey, key1, key2 string) *redis.StringCmd { 257 | a := make([]interface{}, 4) 258 | a[0] = "tr.diff" 259 | a[1] = destKey 260 | a[2] = key1 261 | a[3] = key2 262 | cmd := redis.NewStringCmd(ctx, a...) 263 | _ = tc(ctx, cmd) 264 | return cmd 265 | } 266 | 267 | func (tc tairCmdable) TrSetIntArray(ctx context.Context, key string, fields ...int64) *redis.StringCmd { 268 | a := make([]interface{}, 4) 269 | a[0] = "tr.setintarray" 270 | a[1] = key 271 | for _, f := range fields { 272 | a = append(a, f) 273 | } 274 | cmd := redis.NewStringCmd(ctx, a...) 275 | _ = tc(ctx, cmd) 276 | return cmd 277 | } 278 | 279 | func (tc tairCmdable) TrAppendIntArray(ctx context.Context, key string, fields ...int64) *redis.StringCmd { 280 | a := make([]interface{}, 2) 281 | a[0] = "tr.appendintarray" 282 | a[1] = key 283 | for _, f := range fields { 284 | a = append(a, f) 285 | } 286 | cmd := redis.NewStringCmd(ctx, a...) 287 | _ = tc(ctx, cmd) 288 | return cmd 289 | } 290 | 291 | func (tc tairCmdable) TrSetBitArray(ctx context.Context, key, value string) *redis.FloatCmd { 292 | a := make([]interface{}, 4) 293 | a[0] = "tr.setbitarray" 294 | a[1] = key 295 | a[2] = value 296 | cmd := redis.NewFloatCmd(ctx, a...) 297 | _ = tc(ctx, cmd) 298 | return cmd 299 | } 300 | 301 | func (tc tairCmdable) TrJaccard(ctx context.Context, key1, key2 string) *redis.FloatCmd { 302 | a := make([]interface{}, 3) 303 | a[0] = "tr.jaccard" 304 | a[1] = key1 305 | a[2] = key2 306 | cmd := redis.NewFloatCmd(ctx, a...) 307 | _ = tc(ctx, cmd) 308 | return cmd 309 | } 310 | 311 | func (tc tairCmdable) TrContains(ctx context.Context, key1, key2 string) *redis.BoolCmd { 312 | a := make([]interface{}, 3) 313 | a[0] = "tr.contains" 314 | a[1] = key1 315 | a[2] = key2 316 | cmd := redis.NewBoolCmd(ctx, a...) 317 | _ = tc(ctx, cmd) 318 | return cmd 319 | } 320 | -------------------------------------------------------------------------------- /tair/tairroaring_test.go: -------------------------------------------------------------------------------- 1 | package tair_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/alibaba/tair-go/tair" 7 | "github.com/redis/go-redis/v9" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | ) 11 | 12 | type TairRoaringTestSuite struct { 13 | suite.Suite 14 | tairClient *tair.TairClient 15 | } 16 | 17 | func (suite *TairRoaringTestSuite) SetupTest() { 18 | suite.tairClient = tair.NewTairClient(redisOptions()) 19 | assert.Equal(suite.T(), "OK", suite.tairClient.FlushDB(ctx).Val()) 20 | } 21 | 22 | func (suite *TairRoaringTestSuite) TearDownTest() { 23 | assert.NoError(suite.T(), suite.tairClient.Close()) 24 | } 25 | 26 | func (suite *TairRoaringTestSuite) TestTrSetBit() { 27 | result, err := suite.tairClient.TrSetBit(ctx, "foo", 10, 1).Result() 28 | assert.NoError(suite.T(), err) 29 | assert.Equal(suite.T(), result, int64(0)) 30 | 31 | result2, err2 := suite.tairClient.TrSetBit(ctx, "foo", 20, 1).Result() 32 | assert.NoError(suite.T(), err2) 33 | assert.Equal(suite.T(), result2, int64(0)) 34 | 35 | result3, err3 := suite.tairClient.TrSetBit(ctx, "foo", 30, 1).Result() 36 | assert.NoError(suite.T(), err3) 37 | assert.Equal(suite.T(), result3, int64(0)) 38 | 39 | result4, err4 := suite.tairClient.TrSetBit(ctx, "foo", 30, 0).Result() 40 | assert.NoError(suite.T(), err4) 41 | assert.Equal(suite.T(), result4, int64(1)) 42 | } 43 | 44 | func (suite *TairRoaringTestSuite) TestSetBitsBitCountClearBits() { 45 | result, err := suite.tairClient.TrSetBits(ctx, "foo", 1, 3, 5, 7, 9).Result() 46 | assert.NoError(suite.T(), err) 47 | assert.Equal(suite.T(), result, int64(5)) 48 | 49 | result1, err1 := suite.tairClient.TrBitCount(ctx, "foo").Result() 50 | assert.NoError(suite.T(), err1) 51 | assert.Equal(suite.T(), result1, int64(5)) 52 | 53 | result2, err2 := suite.tairClient.TrSetBits(ctx, "foo", 5, 7, 9, 11, 13).Result() 54 | assert.NoError(suite.T(), err2) 55 | assert.Equal(suite.T(), result2, int64(7)) 56 | 57 | result5, err5 := suite.tairClient.TrBitCount(ctx, "foo").Result() 58 | assert.NoError(suite.T(), err5) 59 | assert.Equal(suite.T(), result5, int64(7)) 60 | 61 | result3, err3 := suite.tairClient.TrClearBits(ctx, "foo", 5, 6, 7, 8, 9).Result() 62 | assert.NoError(suite.T(), err3) 63 | assert.Equal(suite.T(), result3, int64(3)) 64 | 65 | result7, err7 := suite.tairClient.TrBitCount(ctx, "foo").Result() 66 | assert.NoError(suite.T(), err7) 67 | assert.Equal(suite.T(), result7, int64(4)) 68 | 69 | result8, err8 := suite.tairClient.TrGetBits(ctx, "foo", 1, 2, 3, 4, 5).Result() 70 | assert.NoError(suite.T(), err8) 71 | assert.Equal(suite.T(), result8, []int64{1, 0, 1, 0, 0}) 72 | } 73 | 74 | func (suite *TairRoaringTestSuite) TestTrSetBitsTaRange() { 75 | result, err := suite.tairClient.TrSetBits(ctx, "foo", 1, 3, 5, 7, 9).Result() 76 | assert.NoError(suite.T(), err) 77 | assert.Equal(suite.T(), result, int64(5)) 78 | 79 | result1, err1 := suite.tairClient.TrRange(ctx, "foo", 1, 5).Result() 80 | assert.NoError(suite.T(), err1) 81 | assert.Equal(suite.T(), result1, []int64{1, 3, 5}) 82 | 83 | result3, err3 := suite.tairClient.TrRange(ctx, "foo", 0, 4).Result() 84 | assert.NoError(suite.T(), err3) 85 | assert.Equal(suite.T(), result3, []int64{1, 3}) 86 | } 87 | 88 | func (suite *TairRoaringTestSuite) TestTrRange() { 89 | suite.tairClient.TrSetBits(ctx, "foo", 1, 3, 5, 7, 9) 90 | result, err := suite.tairClient.TrRange(ctx, "foo", 1, 5).Result() 91 | assert.NoError(suite.T(), err) 92 | assert.Equal(suite.T(), result, []int64{1, 3, 5}) 93 | result1, err1 := suite.tairClient.TrRange(ctx, "foo", 0, 4).Result() 94 | assert.NoError(suite.T(), err1) 95 | assert.Equal(suite.T(), result1, []int64{1, 3}) 96 | } 97 | 98 | func (suite *TairRoaringTestSuite) TestAppendBitArray() { 99 | result, err := suite.tairClient.TrAppendBitArray(ctx, "foo", 0, "101010101").Result() 100 | assert.NoError(suite.T(), err) 101 | assert.Equal(suite.T(), result, int64(5)) 102 | 103 | result1, err1 := suite.tairClient.TrRange(ctx, "foo", 0, 10).Result() 104 | assert.NoError(suite.T(), err1) 105 | assert.Equal(suite.T(), result1, []int64{1, 3, 5, 7, 9}) 106 | 107 | suite.tairClient.Del(ctx, "foo") 108 | result2, err2 := suite.tairClient.TrAppendBitArray(ctx, "foo", -1, "101010101").Result() 109 | assert.NoError(suite.T(), err2) 110 | assert.Equal(suite.T(), result2, int64(5)) 111 | result3, err3 := suite.tairClient.TrRange(ctx, "foo", 0, 10).Result() 112 | assert.NoError(suite.T(), err3) 113 | assert.Equal(suite.T(), result3, []int64{0, 2, 4, 6, 8}) 114 | } 115 | 116 | func (suite *TairRoaringTestSuite) TestScanCount() { 117 | result1, err1 := suite.tairClient.TrScan(ctx, "no-key", 0).Result() 118 | assert.NoError(suite.T(), err1) 119 | assert.Equal(suite.T(), result1[0], int64(0)) 120 | assert.Equal(suite.T(), result1[1], make([]interface{}, 0)) 121 | 122 | result2, err2 := suite.tairClient.TrSetBits(ctx, "foo", 1, 3, 5, 7, 9).Result() 123 | assert.NoError(suite.T(), err2) 124 | assert.Equal(suite.T(), result2, int64(5)) 125 | 126 | result3, err3 := suite.tairClient.TrScan(ctx, "foo", 0).Result() 127 | assert.NoError(suite.T(), err3) 128 | assert.Equal(suite.T(), result3[0], int64(0)) 129 | assert.Equal(suite.T(), result3[1], append(make([]interface{}, 0), int64(1), int64(3), int64(5), int64(7), int64(9))) 130 | 131 | result4, err4 := suite.tairClient.TrScanCount(ctx, "foo", 4, 2).Result() 132 | assert.NoError(suite.T(), err4) 133 | assert.Equal(suite.T(), result4[0], int64(9)) 134 | assert.Equal(suite.T(), result4[1], append(make([]interface{}, 0), int64(5), int64(7))) 135 | } 136 | 137 | func (suite *TairRoaringTestSuite) TestStatus() { 138 | result, err := suite.tairClient.TrSetBits(ctx, "foo", 1, 2, 3, 4, 5, 6, 7, 8, 9).Result() 139 | assert.NoError(suite.T(), err) 140 | assert.Equal(suite.T(), result, int64(9)) 141 | 142 | result2, err2 := suite.tairClient.TrOptimize(ctx, "foo").Result() 143 | assert.NoError(suite.T(), err2) 144 | assert.Equal(suite.T(), result2, "OK") 145 | 146 | result3, err3 := suite.tairClient.TrBitCount(ctx, "foo").Result() 147 | assert.NoError(suite.T(), err3) 148 | assert.Equal(suite.T(), result3, int64(9)) 149 | 150 | result4, err4 := suite.tairClient.TrBitCountRange(ctx, "foo", 0, 5).Result() 151 | assert.NoError(suite.T(), err4) 152 | assert.Equal(suite.T(), result4, int64(5)) 153 | 154 | result5, err5 := suite.tairClient.TrBitCountRange(ctx, "foo", 9, 20).Result() 155 | assert.NoError(suite.T(), err5) 156 | assert.Equal(suite.T(), result5, int64(1)) 157 | 158 | result6, err6 := suite.tairClient.TrBitPos(ctx, "foo", 1).Result() 159 | assert.NoError(suite.T(), err6) 160 | assert.Equal(suite.T(), result6, int64(1)) 161 | 162 | result7, err7 := suite.tairClient.TrBitPos(ctx, "foo", 1).Result() 163 | assert.NoError(suite.T(), err7) 164 | assert.Equal(suite.T(), result7, int64(1)) 165 | 166 | result8, err8 := suite.tairClient.TrBitPosCount(ctx, "foo", 1, 2).Result() 167 | assert.NoError(suite.T(), err8) 168 | assert.Equal(suite.T(), result8, int64(2)) 169 | 170 | result9, err9 := suite.tairClient.TrBitPosCount(ctx, "foo", 1, -4).Result() 171 | assert.NoError(suite.T(), err9) 172 | assert.Equal(suite.T(), result9, int64(6)) 173 | 174 | result10, err10 := suite.tairClient.TrBitPosCount(ctx, "foo", 0, 1).Result() 175 | assert.NoError(suite.T(), err10) 176 | assert.Equal(suite.T(), result10, int64(0)) 177 | 178 | result11, err11 := suite.tairClient.TrStat(ctx, "foo", false).Result() 179 | assert.NoError(suite.T(), err11) 180 | assert.Equal(suite.T(), result11, "cardinality: 9\r\n"+ 181 | "number of containers: 1\r\n"+ 182 | "max value: 9\r\n"+ 183 | "min value: 1\r\n"+ 184 | "sum value: 45\r\n"+ 185 | "number of array containers: 0\r\n"+ 186 | "\tarray container values: 0\r\n"+ 187 | "\tarray container bytes: 0\r\n"+ 188 | "number of bitset containers: 0\r\n"+ 189 | "\tbitset container values: 0\r\n"+ 190 | "\tbitset container bytes: 0\r\n"+ 191 | "number of run containers: 1\r\n"+ 192 | "\trun container values: 9\r\n"+ 193 | "\trun container bytes: 6\r\n") 194 | } 195 | 196 | func (suite *TairRoaringTestSuite) TestEmptyKey() { 197 | result, err := suite.tairClient.TrRange(ctx, "foo", 0, 4).Result() 198 | assert.NoError(suite.T(), err) 199 | assert.Equal(suite.T(), result, []int64{}) 200 | 201 | result1, err1 := suite.tairClient.TrMin(ctx, "foo").Result() 202 | assert.NoError(suite.T(), err1) 203 | assert.Equal(suite.T(), result1, int64(-1)) 204 | 205 | result2, err2 := suite.tairClient.TrMax(ctx, "foo").Result() 206 | assert.NoError(suite.T(), err2) 207 | assert.Equal(suite.T(), result2, int64(-1)) 208 | 209 | result3, err3 := suite.tairClient.TrRank(ctx, "foo", 1).Result() 210 | assert.NoError(suite.T(), err3) 211 | assert.Equal(suite.T(), result3, int64(-1)) 212 | 213 | _, err4 := suite.tairClient.TrStat(ctx, "foo", false).Result() 214 | assert.Equal(suite.T(), err4, redis.Nil) 215 | 216 | _, err5 := suite.tairClient.TrOptimize(ctx, "foo").Result() 217 | assert.Equal(suite.T(), err5, redis.Nil) 218 | 219 | result6, err6 := suite.tairClient.TrBitCount(ctx, "foo").Result() 220 | assert.NoError(suite.T(), err6) 221 | assert.Equal(suite.T(), result6, int64(0)) 222 | 223 | result7, err7 := suite.tairClient.TrClearBits(ctx, "foo", 1, 3, 5).Result() 224 | assert.NoError(suite.T(), err7) 225 | assert.Equal(suite.T(), result7, int64(0)) 226 | } 227 | 228 | func (suite *TairRoaringTestSuite) TestBitOpTest() { 229 | result, err := suite.tairClient.TrAppendIntArray(ctx, "foo", 1, 3, 5, 7, 9).Result() 230 | assert.NoError(suite.T(), err) 231 | assert.Equal(suite.T(), result, "OK") 232 | 233 | result1, err1 := suite.tairClient.TrAppendIntArray(ctx, "bar", 2, 4, 6, 8, 10).Result() 234 | assert.NoError(suite.T(), err1) 235 | assert.Equal(suite.T(), result1, "OK") 236 | 237 | result2, err2 := suite.tairClient.TrBitOp(ctx, "dest", "OR", "foo", "bar").Result() 238 | assert.NoError(suite.T(), err2) 239 | assert.Equal(suite.T(), result2, int64(10)) 240 | 241 | result3, err3 := suite.tairClient.TrBitOpCard(ctx, "AND", "foo", "bar").Result() 242 | assert.NoError(suite.T(), err3) 243 | assert.Equal(suite.T(), result3, int64(0)) 244 | } 245 | 246 | func (suite *TairRoaringTestSuite) TestGetMany() { 247 | result, err := suite.tairClient.TrAppendIntArray(ctx, "foo", 1, 3, 5, 7, 9, 11, 13, 15, 17, 19).Result() 248 | assert.NoError(suite.T(), err) 249 | assert.Equal(suite.T(), result, "OK") 250 | 251 | result1, err1 := suite.tairClient.TrRange(ctx, "foo", 0, 4).Result() 252 | assert.NoError(suite.T(), err1) 253 | assert.Equal(suite.T(), result1, []int64{1, 3}) 254 | } 255 | 256 | func (suite *TairRoaringTestSuite) TestMultiKey() { 257 | result, err := suite.tairClient.TrSetBits(ctx, "foo", 1, 3, 5, 7, 9).Result() 258 | assert.NoError(suite.T(), err) 259 | assert.Equal(suite.T(), result, int64(5)) 260 | 261 | result1, err1 := suite.tairClient.TrSetBits(ctx, "bar", 2, 4, 6, 8, 10).Result() 262 | assert.NoError(suite.T(), err1) 263 | assert.Equal(suite.T(), result1, int64(5)) 264 | 265 | result2, err2 := suite.tairClient.TrSetRange(ctx, "baz", 1, 10).Result() 266 | assert.NoError(suite.T(), err2) 267 | assert.Equal(suite.T(), result2, int64(10)) 268 | 269 | result3, err3 := suite.tairClient.TrContains(ctx, "foo", "bar").Result() 270 | assert.NoError(suite.T(), err3) 271 | assert.Equal(suite.T(), result3, false) 272 | 273 | result4, err4 := suite.tairClient.TrContains(ctx, "foo", "baz").Result() 274 | assert.NoError(suite.T(), err4) 275 | assert.Equal(suite.T(), result4, true) 276 | 277 | result5, err5 := suite.tairClient.TrJaccard(ctx, "foo", "baz").Result() 278 | assert.NoError(suite.T(), err5) 279 | assert.Equal(suite.T(), result5, 0.5) 280 | 281 | result6, err6 := suite.tairClient.TrDiff(ctx, "result", "foo", "bar").Result() 282 | assert.NoError(suite.T(), err6) 283 | assert.Equal(suite.T(), result6, "OK") 284 | } 285 | 286 | func TestTairRoaringTestSuite(t *testing.T) { 287 | suite.Run(t, new(TairRoaringTestSuite)) 288 | } 289 | -------------------------------------------------------------------------------- /tair/tairsearch.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/redis/go-redis/v9" 7 | ) 8 | 9 | type protocolType int 10 | 11 | const ( 12 | NONE protocolType = iota 13 | ProtoMatch 14 | ProtoCount 15 | ProtoIndex 16 | ProtoShowTime 17 | ) 18 | 19 | func (p protocolType) String() string { 20 | switch p { 21 | case NONE: 22 | return "NONE" 23 | case ProtoMatch: 24 | return "MATCH" 25 | case ProtoCount: 26 | return "COUNT" 27 | case ProtoIndex: 28 | return "INDEX" 29 | case ProtoShowTime: 30 | return "SHOW_TIME" 31 | default: 32 | return "NA" 33 | } 34 | } 35 | 36 | type TftAddDocArgs struct { 37 | arg 38 | } 39 | 40 | func (a TftAddDocArgs) New() *TftAddDocArgs { 41 | a.Set = make(map[string]bool) 42 | return &a 43 | } 44 | 45 | func (a *TftAddDocArgs) JoinArgs(key string, docs map[string]string) []interface{} { 46 | args := make([]interface{}, 0) 47 | args = append(args, key) 48 | for k, v := range docs { 49 | args = append(args, k, v) 50 | } 51 | return args 52 | } 53 | 54 | type TftDelDocArgs struct { 55 | arg 56 | } 57 | 58 | func (a TftDelDocArgs) New() *TftDelDocArgs { 59 | a.Set = make(map[string]bool) 60 | return &a 61 | } 62 | 63 | func (a *TftDelDocArgs) JoinArgs(key string, value ...string) []interface{} { 64 | args := make([]interface{}, 0) 65 | args = append(args, key) 66 | for _, v := range value { 67 | args = append(args, v) 68 | } 69 | return args 70 | } 71 | 72 | type TftScanArgs struct { 73 | arg 74 | match string 75 | count int64 76 | } 77 | 78 | func (a TftScanArgs) New() *TftScanArgs { 79 | a.Set = make(map[string]bool) 80 | return &a 81 | } 82 | 83 | func (a *TftScanArgs) GetArgs() []interface{} { 84 | args := make([]interface{}, 0) 85 | if _, ok := a.Set[ProtoMatch.String()]; ok { 86 | args = append(args, ProtoMatch.String(), a.match) 87 | } 88 | if _, ok := a.Set[ProtoCount.String()]; ok { 89 | args = append(args, ProtoCount.String(), a.count) 90 | } 91 | return args 92 | } 93 | 94 | func (a *TftScanArgs) Match(pattern string) *TftScanArgs { 95 | a.Set[ProtoMatch.String()] = true 96 | a.match = pattern 97 | return a 98 | } 99 | 100 | // 这里为什么要用 Integer 101 | func (a *TftScanArgs) Count(count int64) *TftScanArgs { 102 | a.Set[ProtoCount.String()] = true 103 | a.count = count 104 | return a 105 | } 106 | 107 | type TftAnalyzerArgs struct { 108 | arg 109 | index string 110 | } 111 | 112 | func (a TftAnalyzerArgs) New() *TftAnalyzerArgs { 113 | a.Set = make(map[string]bool) 114 | return &a 115 | } 116 | 117 | func (a *TftAnalyzerArgs) GetArgs() []interface{} { 118 | args := make([]interface{}, 0) 119 | if _, ok := a.Set[ProtoIndex.String()]; ok { 120 | args = append(args, ProtoIndex.String(), a.index) 121 | } 122 | if _, ok := a.Set[ProtoShowTime.String()]; ok { 123 | args = append(args, ProtoShowTime.String()) 124 | } 125 | return args 126 | } 127 | 128 | func (a *TftAnalyzerArgs) HasIndex() bool { 129 | if _, ok := a.Set[ProtoIndex.String()]; ok { 130 | return true 131 | } 132 | return false 133 | } 134 | 135 | func (a *TftAnalyzerArgs) Index(index string) *TftAnalyzerArgs { 136 | a.Set[ProtoIndex.String()] = true 137 | a.index = index 138 | return a 139 | } 140 | 141 | func (a *TftAnalyzerArgs) ShowTime() *TftAnalyzerArgs { 142 | a.Set[ProtoShowTime.String()] = true 143 | return a 144 | } 145 | 146 | func (tc tairCmdable) TftMappingIndex(ctx context.Context, index, request string) *redis.StringCmd { 147 | a := make([]interface{}, 3) 148 | a[0] = "TFT.MAPPINGINDEX" 149 | a[1] = index 150 | a[2] = request 151 | cmd := redis.NewStringCmd(ctx, a...) 152 | _ = tc(ctx, cmd) 153 | return cmd 154 | } 155 | 156 | func (tc tairCmdable) TftCreateIndex(ctx context.Context, index, request string) *redis.StringCmd { 157 | a := make([]interface{}, 3) 158 | a[0] = "TFT.CREATEINDEX" 159 | a[1] = index 160 | a[2] = request 161 | cmd := redis.NewStringCmd(ctx, a...) 162 | _ = tc(ctx, cmd) 163 | return cmd 164 | } 165 | 166 | func (tc tairCmdable) TftUpdateIndex(ctx context.Context, index, request string) *redis.StringCmd { 167 | a := make([]interface{}, 3) 168 | a[0] = "TFT.UPDATEINDEX" 169 | a[1] = index 170 | a[2] = request 171 | cmd := redis.NewStringCmd(ctx, a...) 172 | _ = tc(ctx, cmd) 173 | return cmd 174 | } 175 | 176 | func (tc tairCmdable) TftGetIndexMappings(ctx context.Context, index string) *redis.StringCmd { 177 | a := make([]interface{}, 3) 178 | a[0] = "TFT.GETINDEX" 179 | a[1] = index 180 | a[2] = "MAPPINGS" 181 | cmd := redis.NewStringCmd(ctx, a...) 182 | _ = tc(ctx, cmd) 183 | return cmd 184 | } 185 | 186 | func (tc tairCmdable) TftGetIndex(ctx context.Context, index string) *redis.StringCmd { 187 | a := make([]interface{}, 2) 188 | a[0] = "TFT.GETINDEX" 189 | a[1] = index 190 | cmd := redis.NewStringCmd(ctx, a...) 191 | _ = tc(ctx, cmd) 192 | return cmd 193 | } 194 | 195 | func (tc tairCmdable) TftGetIndexSettings(ctx context.Context, index string) *redis.StringCmd { 196 | a := make([]interface{}, 3) 197 | a[0] = "TFT.GETINDEX" 198 | a[1] = index 199 | a[2] = "SETTINGS" 200 | cmd := redis.NewStringCmd(ctx, a...) 201 | _ = tc(ctx, cmd) 202 | return cmd 203 | } 204 | 205 | func (tc tairCmdable) TftAddDoc(ctx context.Context, index string, request string) *redis.StringCmd { 206 | a := make([]interface{}, 3) 207 | a[0] = "TFT.ADDDOC" 208 | a[1] = index 209 | a[2] = request 210 | cmd := redis.NewStringCmd(ctx, a...) 211 | _ = tc(ctx, cmd) 212 | return cmd 213 | } 214 | 215 | func (tc tairCmdable) TftAddDocWithId(ctx context.Context, index string, request string, docId string) *redis.StringCmd { 216 | a := make([]interface{}, 5) 217 | a[0] = "TFT.ADDDOC" 218 | a[1] = index 219 | a[2] = request 220 | a[3] = "WITH_ID" 221 | a[4] = docId 222 | cmd := redis.NewStringCmd(ctx, a...) 223 | _ = tc(ctx, cmd) 224 | return cmd 225 | } 226 | 227 | func (tc tairCmdable) TftMAddDoc(ctx context.Context, index string, docs map[string]string) *redis.StringCmd { 228 | a := make([]interface{}, 1) 229 | a[0] = "TFT.MADDDOC" 230 | a = append(a, TftAddDocArgs{}.New().JoinArgs(index, docs)...) 231 | cmd := redis.NewStringCmd(ctx, a...) 232 | _ = tc(ctx, cmd) 233 | return cmd 234 | } 235 | 236 | func (tc tairCmdable) TftUpdateDocField(ctx context.Context, index, docId, docContent string) *redis.StringCmd { 237 | a := make([]interface{}, 4) 238 | a[0] = "TFT.UPDATEDOCFIELD" 239 | a[1] = index 240 | a[2] = docId 241 | a[3] = docContent 242 | cmd := redis.NewStringCmd(ctx, a...) 243 | _ = tc(ctx, cmd) 244 | return cmd 245 | } 246 | func (tc tairCmdable) TftIncrLongDocField(ctx context.Context, index, docId, docContent string, value int64) *redis.IntCmd { 247 | a := make([]interface{}, 5) 248 | a[0] = "TFT.INCRLONGDOCFIELD" 249 | a[1] = index 250 | a[2] = docId 251 | a[3] = docContent 252 | a[4] = value 253 | cmd := redis.NewIntCmd(ctx, a...) 254 | _ = tc(ctx, cmd) 255 | return cmd 256 | } 257 | func (tc tairCmdable) TftIncrFloatDocField(ctx context.Context, index, docId, docContent string, value float64) *redis.FloatCmd { 258 | a := make([]interface{}, 5) 259 | a[0] = "TFT.INCRFLOATDOCFIELD" 260 | a[1] = index 261 | a[2] = docId 262 | a[3] = docContent 263 | a[4] = value 264 | cmd := redis.NewFloatCmd(ctx, a...) 265 | _ = tc(ctx, cmd) 266 | return cmd 267 | } 268 | 269 | func (tc tairCmdable) TftDelDocField(ctx context.Context, index, docId string, field ...string) *redis.IntCmd { 270 | a := make([]interface{}, 3) 271 | a[0] = "TFT.DELDOCFIELD" 272 | a[1] = index 273 | a[2] = docId 274 | for _, f := range field { 275 | a = append(a, f) 276 | } 277 | cmd := redis.NewIntCmd(ctx, a...) 278 | _ = tc(ctx, cmd) 279 | return cmd 280 | } 281 | 282 | func (tc tairCmdable) TftGetDoc(ctx context.Context, index, docId string) *redis.StringCmd { 283 | a := make([]interface{}, 3) 284 | a[0] = "TFT.GETDOC" 285 | a[1] = index 286 | a[2] = docId 287 | cmd := redis.NewStringCmd(ctx, a...) 288 | _ = tc(ctx, cmd) 289 | return cmd 290 | } 291 | 292 | func (tc tairCmdable) TftDelDoc(ctx context.Context, index string, docId ...string) *redis.StringCmd { 293 | a := make([]interface{}, 2) 294 | a[0] = "TFT.DELDOC" 295 | a[1] = index 296 | for _, d := range docId { 297 | a = append(a, d) 298 | } 299 | cmd := redis.NewStringCmd(ctx, a...) 300 | _ = tc(ctx, cmd) 301 | return cmd 302 | } 303 | 304 | func (tc tairCmdable) TftDelAll(ctx context.Context, index string) *redis.StringCmd { 305 | a := make([]interface{}, 2) 306 | a[0] = "TFT.DELALL" 307 | a[1] = index 308 | cmd := redis.NewStringCmd(ctx, a...) 309 | _ = tc(ctx, cmd) 310 | return cmd 311 | } 312 | 313 | func (tc tairCmdable) TftSearch(ctx context.Context, index string, request string) *redis.StringCmd { 314 | a := make([]interface{}, 3) 315 | a[0] = "TFT.SEARCH" 316 | a[1] = index 317 | a[2] = request 318 | cmd := redis.NewStringCmd(ctx, a...) 319 | _ = tc(ctx, cmd) 320 | return cmd 321 | } 322 | 323 | func (tc tairCmdable) TftSearchUseCache(ctx context.Context, index string, request string, useCache bool) *redis.StringCmd { 324 | a := make([]interface{}, 3) 325 | a[0] = "TFT.SEARCH" 326 | a[1] = index 327 | a[2] = request 328 | if useCache { 329 | a = append(a, "USE_CACHE") 330 | } 331 | cmd := redis.NewStringCmd(ctx, a...) 332 | _ = tc(ctx, cmd) 333 | return cmd 334 | } 335 | 336 | func (tc tairCmdable) TftMSearch(ctx context.Context, indexCount int64, request string, index ...string) *redis.StringCmd { 337 | a := make([]interface{}, 2) 338 | a[0] = "TFT.MSEARCH" 339 | a[1] = indexCount 340 | for _, d := range index { 341 | a = append(a, d) 342 | } 343 | a = append(a, request) 344 | cmd := redis.NewStringCmd(ctx, a...) 345 | _ = tc(ctx, cmd) 346 | return cmd 347 | } 348 | 349 | func (tc tairCmdable) TftExists(ctx context.Context, index string, docId string) *redis.IntCmd { 350 | a := make([]interface{}, 3) 351 | a[0] = "TFT.EXISTS" 352 | a[1] = index 353 | a[2] = docId 354 | cmd := redis.NewIntCmd(ctx, a...) 355 | _ = tc(ctx, cmd) 356 | return cmd 357 | } 358 | 359 | func (tc tairCmdable) TftDocNum(ctx context.Context, index string) *redis.IntCmd { 360 | a := make([]interface{}, 2) 361 | a[0] = "TFT.DOCNUM" 362 | a[1] = index 363 | cmd := redis.NewIntCmd(ctx, a...) 364 | _ = tc(ctx, cmd) 365 | return cmd 366 | } 367 | 368 | func (tc tairCmdable) TftScanDocId(ctx context.Context, index string, cursor string) *redis.SliceCmd { 369 | a := make([]interface{}, 3) 370 | a[0] = "TFT.SCANDOCID" 371 | a[1] = index 372 | a[2] = cursor 373 | cmd := redis.NewSliceCmd(ctx, a...) 374 | _ = tc(ctx, cmd) 375 | return cmd 376 | } 377 | 378 | func (tc tairCmdable) TftScanDocIdArgs(ctx context.Context, index string, cursor string, a *TftScanArgs) *redis.SliceCmd { 379 | args := make([]interface{}, 3) 380 | args[0] = "TFT.SCANDOCID" 381 | args[1] = index 382 | args[2] = cursor 383 | args = append(args, a.GetArgs()...) 384 | cmd := redis.NewSliceCmd(ctx, args...) 385 | _ = tc(ctx, cmd) 386 | return cmd 387 | } 388 | 389 | func (tc tairCmdable) TftAnalyzer(ctx context.Context, analyzerName string, text string) *redis.StringCmd { 390 | a := make([]interface{}, 3) 391 | a[0] = "TFT.ANALYZER" 392 | a[1] = analyzerName 393 | a[2] = text 394 | cmd := redis.NewStringCmd(ctx, a...) 395 | _ = tc(ctx, cmd) 396 | return cmd 397 | } 398 | 399 | func (tc tairCmdable) TftAnalyzerWithArgs(ctx context.Context, analyzerName string, text string, a *TftAnalyzerArgs) *redis.StringCmd { 400 | args := make([]interface{}, 3) 401 | args[0] = "TFT.ANALYZER" 402 | args[1] = analyzerName 403 | args[2] = text 404 | args = append(args, a.GetArgs()...) 405 | cmd := redis.NewStringCmd(ctx, args...) 406 | if a.HasIndex() { 407 | cmd.SetFirstKeyPos(4) 408 | } 409 | _ = tc(ctx, cmd) 410 | return cmd 411 | } 412 | 413 | func (tc tairCmdable) TftExplaincost(ctx context.Context, index string, request string) *redis.StringCmd { 414 | a := make([]interface{}, 3) 415 | a[0] = "TFT.EXPLAINCOST" 416 | a[1] = index 417 | a[2] = request 418 | cmd := redis.NewStringCmd(ctx, a...) 419 | _ = tc(ctx, cmd) 420 | return cmd 421 | } 422 | 423 | func (tc tairCmdable) TftExplainscore(ctx context.Context, index string, request string, docId ...string) *redis.StringCmd { 424 | a := make([]interface{}, 3) 425 | a[0] = "TFT.EXPLAINSCORE" 426 | a[1] = index 427 | a[2] = request 428 | for _, id := range docId { 429 | a = append(a, id) 430 | } 431 | cmd := redis.NewStringCmd(ctx, a...) 432 | _ = tc(ctx, cmd) 433 | return cmd 434 | } 435 | 436 | func (tc tairCmdable) TftAddSug(ctx context.Context, index string, textWeight map[string]int64) *redis.IntCmd { 437 | args := make([]interface{}, 2) 438 | args[0] = "TFT.ADDSUG" 439 | args[1] = index 440 | for k, v := range textWeight { 441 | args = append(args, k, v) 442 | } 443 | cmd := redis.NewIntCmd(ctx, args...) 444 | _ = tc(ctx, cmd) 445 | return cmd 446 | } 447 | 448 | func (tc tairCmdable) TftDelSug(ctx context.Context, index string, text ...string) *redis.IntCmd { 449 | args := make([]interface{}, 3) 450 | args[0] = "TFT.DELSUG" 451 | args[1] = index 452 | for _, t := range text { 453 | args = append(args, t) 454 | } 455 | cmd := redis.NewIntCmd(ctx, args...) 456 | _ = tc(ctx, cmd) 457 | return cmd 458 | } 459 | 460 | func (tc tairCmdable) TftSugSum(ctx context.Context, index string) *redis.IntCmd { 461 | args := make([]interface{}, 2) 462 | args[0] = "TFT.SUGNUM" 463 | args[1] = index 464 | cmd := redis.NewIntCmd(ctx, args...) 465 | _ = tc(ctx, cmd) 466 | return cmd 467 | } 468 | 469 | func (tc tairCmdable) TftGetSug(ctx context.Context, index string, prefix string, count int8, fuzzy bool) *redis.StringSliceCmd { 470 | args := make([]interface{}, 5) 471 | args[0] = "TFT.GETSUG" 472 | args[1] = index 473 | args[2] = prefix 474 | args[3] = "MAX_COUNT" 475 | args[4] = count 476 | if fuzzy { 477 | args = append(args, "FUZZY") 478 | } 479 | cmd := redis.NewStringSliceCmd(ctx, args...) 480 | _ = tc(ctx, cmd) 481 | return cmd 482 | } 483 | 484 | func (tc tairCmdable) TftGetAllSug(ctx context.Context, index string) *redis.StringSliceCmd { 485 | args := make([]interface{}, 2) 486 | args[0] = "TFT.GETALLSUGS" 487 | args[1] = index 488 | cmd := redis.NewStringSliceCmd(ctx, args...) 489 | _ = tc(ctx, cmd) 490 | return cmd 491 | } 492 | -------------------------------------------------------------------------------- /tair/tairstring.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/redis/go-redis/v9" 8 | ) 9 | 10 | type ExSetArgs struct { 11 | arg 12 | 13 | xx string 14 | nx string 15 | 16 | ex time.Duration 17 | px time.Duration 18 | exAt time.Time 19 | pxAt time.Time 20 | 21 | ver int64 22 | abs int64 23 | 24 | flags int 25 | keepttl string 26 | } 27 | 28 | func (a ExSetArgs) New() *ExSetArgs { 29 | a.Set = make(map[string]bool) 30 | return &a 31 | } 32 | 33 | func (a *ExSetArgs) Xx() *ExSetArgs { 34 | a.Set[XX] = true 35 | return a 36 | } 37 | 38 | func (a *ExSetArgs) Nx() *ExSetArgs { 39 | a.Set[NX] = true 40 | return a 41 | } 42 | 43 | func (a *ExSetArgs) Ex(ex time.Duration) *ExSetArgs { 44 | a.Set[EX] = true 45 | a.ex = ex 46 | return a 47 | } 48 | 49 | func (a *ExSetArgs) Px(px time.Duration) *ExSetArgs { 50 | a.Set[PX] = true 51 | a.px = px 52 | return a 53 | } 54 | 55 | func (a *ExSetArgs) ExAt(exAt time.Time) *ExSetArgs { 56 | a.Set[EXAT] = true 57 | a.exAt = exAt 58 | return a 59 | } 60 | 61 | func (a *ExSetArgs) PxAt(pxAt time.Time) *ExSetArgs { 62 | a.Set[PXAT] = true 63 | a.pxAt = pxAt 64 | return a 65 | } 66 | 67 | func (a *ExSetArgs) Ver(ver int64) *ExSetArgs { 68 | a.Set[VER] = true 69 | a.ver = ver 70 | return a 71 | } 72 | 73 | func (a *ExSetArgs) Abs(abs int64) *ExSetArgs { 74 | a.Set[ABS] = true 75 | a.abs = abs 76 | return a 77 | 78 | } 79 | 80 | func (a *ExSetArgs) Flags(flags int) *ExSetArgs { 81 | a.Set[FLAGS] = true 82 | a.flags = flags 83 | return a 84 | } 85 | 86 | func (a *ExSetArgs) KeepTTL() *ExSetArgs { 87 | a.Set[KEEPTTL] = true 88 | return a 89 | } 90 | 91 | func (a *ExSetArgs) GetArgs() []interface{} { 92 | args := make([]interface{}, 0) 93 | if _, ok := a.Set[XX]; ok { 94 | args = append(args, XX) 95 | } 96 | if _, ok := a.Set[NX]; ok { 97 | args = append(args, NX) 98 | } 99 | if _, ok := a.Set[EX]; ok { 100 | args = append(args, EX, toSec(a.ex)) 101 | } 102 | if _, ok := a.Set[PX]; ok { 103 | args = append(args, PX, toMs(a.px)) 104 | } 105 | if _, ok := a.Set[EXAT]; ok { 106 | args = append(args, EXAT, a.exAt.Unix()) 107 | } 108 | if _, ok := a.Set[PXAT]; ok { 109 | args = append(args, PXAT, a.pxAt.Unix()) 110 | } 111 | if _, ok := a.Set[VER]; ok { 112 | args = append(args, VER, a.ver) 113 | } 114 | if _, ok := a.Set[ABS]; ok { 115 | args = append(args, ABS, a.abs) 116 | } 117 | if _, ok := a.Set[FLAGS]; ok { 118 | args = append(args, FLAGS, a.flags) 119 | } 120 | if _, ok := a.Set[KEEPTTL]; ok { 121 | args = append(args, KEEPTTL) 122 | } 123 | return args 124 | } 125 | 126 | type CasArgs struct { 127 | arg 128 | 129 | ex time.Duration 130 | exAt time.Time 131 | px time.Duration 132 | pxAt time.Time 133 | keepttl string 134 | } 135 | 136 | func (a CasArgs) New() *CasArgs { 137 | a.Set = make(map[string]bool) 138 | return &a 139 | } 140 | 141 | func (a *CasArgs) Ex(ex time.Duration) *CasArgs { 142 | a.Set[EX] = true 143 | a.ex = ex 144 | return a 145 | } 146 | 147 | func (a *CasArgs) ExAt(exAt time.Time) *CasArgs { 148 | a.Set[EXAT] = true 149 | a.exAt = exAt 150 | return a 151 | } 152 | 153 | func (a *CasArgs) Px(px time.Duration) *CasArgs { 154 | a.Set[PX] = true 155 | a.px = px 156 | return a 157 | } 158 | 159 | func (a *CasArgs) PxAt(pxAt time.Time) *CasArgs { 160 | a.Set[PXAT] = true 161 | a.pxAt = pxAt 162 | return a 163 | } 164 | 165 | func (a *CasArgs) KeppTTL() *CasArgs { 166 | a.Set[KEEPTTL] = true 167 | return a 168 | } 169 | 170 | func (a *CasArgs) GetArgs() []interface{} { 171 | args := make([]interface{}, 0) 172 | 173 | if _, ok := a.Set[EXAT]; ok { 174 | args = append(args, EXAT, a.exAt.Unix()) 175 | } 176 | if _, ok := a.Set[PXAT]; ok { 177 | args = append(args, PXAT, a.pxAt.Unix()) 178 | } 179 | if _, ok := a.Set[EX]; ok { 180 | args = append(args, EX, toSec(a.ex)) 181 | } 182 | if _, ok := a.Set[PX]; ok { 183 | args = append(args, PX, toMs(a.px)) 184 | } 185 | if _, ok := a.Set[KEEPTTL]; ok { 186 | args = append(args, KEEPTTL) 187 | } 188 | return args 189 | } 190 | 191 | type ExIncrByArgs struct { 192 | arg 193 | 194 | xx string 195 | nx string 196 | 197 | ex time.Duration 198 | px time.Duration 199 | exAt time.Time 200 | pxAt time.Time 201 | 202 | ver int64 203 | abs int64 204 | 205 | min int64 206 | max int64 207 | 208 | def int64 209 | noNegative string 210 | keepttl string 211 | } 212 | 213 | func (a ExIncrByArgs) New() *ExIncrByArgs { 214 | a.Set = make(map[string]bool) 215 | return &a 216 | } 217 | 218 | func (a *ExIncrByArgs) Xx() *ExIncrByArgs { 219 | a.Set[XX] = true 220 | return a 221 | } 222 | 223 | func (a *ExIncrByArgs) Nx() *ExIncrByArgs { 224 | a.Set[NX] = true 225 | return a 226 | } 227 | 228 | func (a *ExIncrByArgs) Ex(ex time.Duration) *ExIncrByArgs { 229 | a.Set[EX] = true 230 | a.ex = ex 231 | return a 232 | } 233 | 234 | func (a *ExIncrByArgs) Px(px time.Duration) *ExIncrByArgs { 235 | a.Set[PX] = true 236 | a.px = px 237 | return a 238 | } 239 | 240 | func (a *ExIncrByArgs) ExAt(exAt time.Time) *ExIncrByArgs { 241 | a.Set[EXAT] = true 242 | a.exAt = exAt 243 | return a 244 | } 245 | 246 | func (a *ExIncrByArgs) PxAt(pxAt time.Time) *ExIncrByArgs { 247 | a.Set[PXAT] = true 248 | a.pxAt = pxAt 249 | return a 250 | } 251 | 252 | func (a *ExIncrByArgs) Ver(ver int64) *ExIncrByArgs { 253 | a.Set[VER] = true 254 | a.ver = ver 255 | return a 256 | } 257 | 258 | func (a *ExIncrByArgs) Abs(ver int64) *ExIncrByArgs { 259 | a.Set[ABS] = true 260 | a.ver = ver 261 | return a 262 | } 263 | 264 | func (a *ExIncrByArgs) Min(min int64) *ExIncrByArgs { 265 | a.Set[MIN] = true 266 | a.min = min 267 | return a 268 | } 269 | 270 | func (a *ExIncrByArgs) Max(max int64) *ExIncrByArgs { 271 | a.Set[MAX] = true 272 | a.max = max 273 | return a 274 | } 275 | 276 | func (a *ExIncrByArgs) Def(def int64) *ExIncrByArgs { 277 | a.Set[DEF] = true 278 | a.def = def 279 | return a 280 | } 281 | 282 | func (a *ExIncrByArgs) SetNoNegative() *ExIncrByArgs { 283 | a.Set[NONEGATIVE] = true 284 | return a 285 | } 286 | 287 | func (a *ExIncrByArgs) KeepTTL() *ExIncrByArgs { 288 | a.Set[KEEPTTL] = true 289 | return a 290 | } 291 | 292 | func (a ExIncrByArgs) GetArgs() []interface{} { 293 | args := make([]interface{}, 0) 294 | if _, ok := a.Set[XX]; ok { 295 | args = append(args, XX) 296 | } 297 | if _, ok := a.Set[NX]; ok { 298 | args = append(args, NX) 299 | } 300 | if _, ok := a.Set[EX]; ok { 301 | args = append(args, EX, toSec(a.ex)) 302 | } 303 | if _, ok := a.Set[PX]; ok { 304 | args = append(args, PX, toMs(a.px)) 305 | } 306 | if _, ok := a.Set[EXAT]; ok { 307 | args = append(args, EXAT, a.exAt.Unix()) 308 | } 309 | if _, ok := a.Set[PXAT]; ok { 310 | args = append(args, PXAT, a.pxAt.Unix()) 311 | } 312 | if _, ok := a.Set[VER]; ok { 313 | args = append(args, VER, a.ver) 314 | } 315 | if _, ok := a.Set[ABS]; ok { 316 | args = append(args, ABS, a.abs) 317 | } 318 | if _, ok := a.Set[MIN]; ok { 319 | args = append(args, MIN, a.min) 320 | } 321 | if _, ok := a.Set[MAX]; ok { 322 | args = append(args, MAX, a.max) 323 | } 324 | if _, ok := a.Set[DEF]; ok { 325 | args = append(args, DEF, a.def) 326 | } 327 | if _, ok := a.Set[NONEGATIVE]; ok { 328 | args = append(args, NONEGATIVE) 329 | } 330 | if _, ok := a.Set[KEEPTTL]; ok { 331 | args = append(args, KEEPTTL) 332 | } 333 | return args 334 | } 335 | 336 | func (tc tairCmdable) Cas(ctx context.Context, key string, oldVal, newVal interface{}) *redis.IntCmd { 337 | args := make([]interface{}, 4) 338 | args[0] = "cas" 339 | args[1] = key 340 | args[2] = oldVal 341 | args[3] = newVal 342 | cmd := redis.NewIntCmd(ctx, args...) 343 | _ = tc(ctx, cmd) 344 | return cmd 345 | } 346 | 347 | func (tc tairCmdable) CasArgs(ctx context.Context, key string, oldVal, newVal interface{}, a *CasArgs) *redis.IntCmd { 348 | args := make([]interface{}, 4) 349 | args[0] = "cas" 350 | args[1] = key 351 | args[2] = oldVal 352 | args[3] = newVal 353 | args = append(args, a.GetArgs()...) 354 | cmd := redis.NewIntCmd(ctx, args...) 355 | _ = tc(ctx, cmd) 356 | return cmd 357 | } 358 | 359 | func (tc tairCmdable) Cad(ctx context.Context, key string, value interface{}) *redis.IntCmd { 360 | args := make([]interface{}, 3) 361 | args[0] = "cad" 362 | args[1] = key 363 | args[2] = value 364 | cmd := redis.NewIntCmd(ctx, args...) 365 | _ = tc(ctx, cmd) 366 | return cmd 367 | } 368 | 369 | func (tc tairCmdable) ExSet(ctx context.Context, key string, value interface{}) *redis.StatusCmd { 370 | args := make([]interface{}, 3) 371 | args[0] = "exset" 372 | args[1] = key 373 | args[2] = value 374 | cmd := redis.NewStatusCmd(ctx, args...) 375 | _ = tc(ctx, cmd) 376 | return cmd 377 | } 378 | 379 | func (tc tairCmdable) ExSetArgs(ctx context.Context, key string, value interface{}, a *ExSetArgs) *redis.StatusCmd { 380 | args := make([]interface{}, 3) 381 | args[0] = "exset" 382 | args[1] = key 383 | args[2] = value 384 | args = append(args, a.GetArgs()...) 385 | cmd := redis.NewStatusCmd(ctx, args...) 386 | _ = tc(ctx, cmd) 387 | return cmd 388 | } 389 | 390 | func (tc tairCmdable) ExSetWithVersion(ctx context.Context, key string, value interface{}, exSetParam ExSetArgs) *redis.IntCmd { 391 | args := make([]interface{}, 4) 392 | args[0] = "exset" 393 | args[1] = key 394 | args[2] = value 395 | args[3] = "withversion" 396 | args = append(args, exSetParam.GetArgs()...) 397 | cmd := redis.NewIntCmd(ctx, args...) 398 | _ = tc(ctx, cmd) 399 | return cmd 400 | } 401 | 402 | func (tc tairCmdable) ExSetVer(ctx context.Context, key string, version int64) *redis.IntCmd { 403 | args := make([]interface{}, 3) 404 | args[0] = "exset" 405 | args[1] = key 406 | args[2] = version 407 | cmd := redis.NewIntCmd(ctx, args...) 408 | _ = tc(ctx, cmd) 409 | return cmd 410 | } 411 | 412 | func (tc tairCmdable) ExGet(ctx context.Context, key string) *redis.SliceCmd { 413 | cmd := redis.NewSliceCmd(ctx, "exget", key) 414 | _ = tc(ctx, cmd) 415 | return cmd 416 | } 417 | 418 | func (tc tairCmdable) ExGetWithFlags(ctx context.Context, key string) *redis.SliceCmd { 419 | cmd := redis.NewSliceCmd(ctx, "exget", key, "withflags") 420 | _ = tc(ctx, cmd) 421 | return cmd 422 | } 423 | 424 | func (tc tairCmdable) ExIncrBy(ctx context.Context, key string, incr int64) *redis.IntCmd { 425 | cmd := redis.NewIntCmd(ctx, "exincrby", key, incr) 426 | _ = tc(ctx, cmd) 427 | return cmd 428 | } 429 | 430 | func (tc tairCmdable) ExIncrByArgs(ctx context.Context, key string, incr int64, a *ExIncrByArgs) *redis.IntCmd { 431 | args := make([]interface{}, 3) 432 | args[0] = "exincrby" 433 | args[1] = key 434 | args[2] = incr 435 | args = append(args, a.GetArgs()...) 436 | cmd := redis.NewIntCmd(ctx, args...) 437 | _ = tc(ctx, cmd) 438 | return cmd 439 | } 440 | 441 | func (tc tairCmdable) ExIncrByWithVersion(ctx context.Context, key string, incr int64, param ExIncrByArgs) *redis.SliceCmd { 442 | args := make([]interface{}, 3) 443 | args[0] = "exincrby" 444 | args[1] = key 445 | args[2] = incr 446 | args = append(args, param.GetArgs()...) 447 | cmd := redis.NewSliceCmd(ctx, args...) 448 | _ = tc(ctx, cmd) 449 | return cmd 450 | } 451 | 452 | func (tc tairCmdable) ExIncrByFloat(ctx context.Context, key string, incr float64) *redis.FloatCmd { 453 | args := make([]interface{}, 3) 454 | args[0] = "exincrbyfloat" 455 | args[1] = key 456 | args[2] = incr 457 | cmd := redis.NewFloatCmd(ctx, args...) 458 | _ = tc(ctx, cmd) 459 | return cmd 460 | } 461 | 462 | func (tc tairCmdable) ExIncrByFloatArgs(ctx context.Context, key string, incr float64, a *ExIncrByArgs) *redis.FloatCmd { 463 | args := make([]interface{}, 3) 464 | args[0] = "exincrbyfloat" 465 | args[1] = key 466 | args[2] = incr 467 | args = append(args, a.GetArgs()...) 468 | cmd := redis.NewFloatCmd(ctx, args...) 469 | _ = tc(ctx, cmd) 470 | return cmd 471 | } 472 | 473 | func (tc tairCmdable) ExCas(ctx context.Context, key string, newVal interface{}, version int64) *redis.SliceCmd { 474 | args := make([]interface{}, 4) 475 | args[0] = "excas" 476 | args[1] = key 477 | args[2] = newVal 478 | args[3] = version 479 | cmd := redis.NewSliceCmd(ctx, args...) 480 | _ = tc(ctx, cmd) 481 | return cmd 482 | } 483 | 484 | func (tc tairCmdable) ExCad(ctx context.Context, key string, version int) *redis.IntCmd { 485 | args := make([]interface{}, 3) 486 | args[0] = "excad" 487 | args[1] = key 488 | args[2] = version 489 | cmd := redis.NewIntCmd(ctx, args...) 490 | _ = tc(ctx, cmd) 491 | return cmd 492 | } 493 | 494 | func (tc tairCmdable) ExAppend(ctx context.Context, key string, value interface{}, nxxx, verAbs string, version int64) *redis.IntCmd { 495 | args := make([]interface{}, 6) 496 | args[0] = "exappend" 497 | args[1] = key 498 | args[2] = value 499 | args[3] = nxxx 500 | args[4] = verAbs 501 | args[5] = version 502 | cmd := redis.NewIntCmd(ctx, args...) 503 | _ = tc(ctx, cmd) 504 | return cmd 505 | } 506 | 507 | func (tc tairCmdable) ExPreAppend(ctx context.Context, key string, value interface{}, nxxx, verAbs string, version int) *redis.IntCmd { 508 | args := make([]interface{}, 6) 509 | args[0] = "exprepend" 510 | args[1] = key 511 | args[2] = value 512 | args[3] = nxxx 513 | args[4] = verAbs 514 | args[5] = version 515 | cmd := redis.NewIntCmd(ctx, args...) 516 | _ = tc(ctx, cmd) 517 | return cmd 518 | } 519 | 520 | func (tc tairCmdable) ExGae(ctx context.Context, key string, expxwithat string, time time.Duration) *redis.SliceCmd { 521 | args := make([]interface{}, 4) 522 | args[0] = "exgae" 523 | args[1] = key 524 | args[2] = expxwithat 525 | args[3] = time 526 | cmd := redis.NewSliceCmd(ctx, args...) 527 | _ = tc(ctx, cmd) 528 | return cmd 529 | } 530 | -------------------------------------------------------------------------------- /tair/tairstring_test.go: -------------------------------------------------------------------------------- 1 | package tair_test 2 | 3 | import ( 4 | "github.com/alibaba/tair-go/tair" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/stretchr/testify/suite" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | type TairStringTestSuite struct { 12 | suite.Suite 13 | tairClient *tair.TairClient 14 | } 15 | 16 | func (suite *TairStringTestSuite) SetupTest() { 17 | suite.tairClient = tair.NewTairClient(redisOptions()) 18 | assert.Equal(suite.T(), "OK", suite.tairClient.FlushDB(ctx).Val()) 19 | } 20 | 21 | func (suite *TairStringTestSuite) TearDownTest() { 22 | assert.NoError(suite.T(), suite.tairClient.Close()) 23 | } 24 | 25 | func (suite *TairStringTestSuite) TestCas() { 26 | suite.tairClient.Set(ctx, "k1", "v1", 0) 27 | n, err := suite.tairClient.Cas(ctx, "k1", "v2", "v3").Result() 28 | assert.NoError(suite.T(), err) 29 | assert.Equal(suite.T(), n, int64(0)) 30 | 31 | n, err = suite.tairClient.Cas(ctx, "k1", "v1", "v3").Result() 32 | assert.NoError(suite.T(), err) 33 | assert.Equal(suite.T(), n, int64(1)) 34 | 35 | res, err := suite.tairClient.Get(ctx, "k1").Result() 36 | assert.NoError(suite.T(), err) 37 | assert.Equal(suite.T(), res, "v3") 38 | } 39 | 40 | func (suite *TairStringTestSuite) TestCasArgs() { 41 | suite.tairClient.Set(ctx, "foo", "bzz", 0) 42 | suite.tairClient.CasArgs(ctx, "foo", "bzz", "too", tair.CasArgs{}.New().Ex(1)) 43 | 44 | result, err := suite.tairClient.Get(ctx, "foo").Result() 45 | assert.Equal(suite.T(), result, "too") 46 | assert.NoError(suite.T(), err) 47 | time.Sleep(time.Duration(2) * time.Second) 48 | 49 | result1, err1 := suite.tairClient.Get(ctx, "foo").Result() 50 | assert.Error(suite.T(), err1) 51 | assert.Equal(suite.T(), result1, "") 52 | } 53 | 54 | func (suite *TairStringTestSuite) TestCad() { 55 | suite.tairClient.Set(ctx, "foo", "bar", 0) 56 | res, err := suite.tairClient.Cad(ctx, "foo", "bzz").Result() 57 | assert.NoError(suite.T(), err) 58 | assert.Equal(suite.T(), res, int64(0)) 59 | 60 | res1, err1 := suite.tairClient.Cad(ctx, "foo", "bar").Result() 61 | assert.NoError(suite.T(), err1) 62 | assert.Equal(suite.T(), res1, int64(1)) 63 | } 64 | 65 | func (suite *TairStringTestSuite) TestExSetArgs() { 66 | result2, err2 := suite.tairClient.ExSetArgs(ctx, "foo", "bar", tair.ExSetArgs{}.New().Xx()).Result() 67 | assert.Error(suite.T(), err2) 68 | assert.Equal(suite.T(), result2, "") 69 | 70 | result3, err3 := suite.tairClient.ExSetArgs(ctx, "foo", "bar", tair.ExSetArgs{}.New().Nx()).Result() 71 | assert.NoError(suite.T(), err3) 72 | assert.Equal(suite.T(), result3, "OK") 73 | } 74 | 75 | func (suite *TairStringTestSuite) TestExGet() { 76 | result2, err2 := suite.tairClient.ExSetArgs(ctx, "foo", "bar", tair.ExSetArgs{}.New().Abs(100)).Result() 77 | assert.NoError(suite.T(), err2) 78 | assert.Equal(suite.T(), result2, "OK") 79 | 80 | result4, err4 := suite.tairClient.ExGet(ctx, "foo").Result() 81 | assert.NoError(suite.T(), err4) 82 | assert.Equal(suite.T(), result4[0], "bar") 83 | assert.Equal(suite.T(), result4[1], int64(100)) 84 | } 85 | 86 | func (suite *TairStringTestSuite) TestExGetWithFlags() { 87 | a := tair.ExSetArgs{}.New() 88 | a.Abs(88) 89 | a.Flags(99) 90 | exSetRes, err := suite.tairClient.ExSetArgs(ctx, "k", "v", a).Result() 91 | assert.NoError(suite.T(), err) 92 | assert.Equal(suite.T(), exSetRes, "OK") 93 | 94 | res, err := suite.tairClient.ExGetWithFlags(ctx, "k").Result() 95 | assert.NoError(suite.T(), err) 96 | assert.Equal(suite.T(), res[0], "v") 97 | assert.Equal(suite.T(), res[1], int64(88)) 98 | assert.Equal(suite.T(), res[2], int64(99)) 99 | } 100 | 101 | func (suite *TairStringTestSuite) TestExIncrBy() { 102 | result, err := suite.tairClient.ExIncrBy(ctx, "foo", 100).Result() 103 | assert.NoError(suite.T(), err) 104 | assert.Equal(suite.T(), result, int64(100)) 105 | 106 | a := tair.ExIncrByArgs{}.New() 107 | a.Max(150) 108 | _, err1 := suite.tairClient.ExIncrByArgs(ctx, "foo", 100, a).Result() 109 | assert.Error(suite.T(), err1) 110 | } 111 | 112 | func (suite *TairStringTestSuite) TestExIncrByArgs() { 113 | result, err := suite.tairClient.ExIncrBy(ctx, "foo", 100).Result() 114 | assert.NoError(suite.T(), err) 115 | assert.Equal(suite.T(), result, int64(100)) 116 | 117 | a := tair.ExIncrByArgs{}.New() 118 | a.Max(300) 119 | res1, err1 := suite.tairClient.ExIncrByArgs(ctx, "foo", 100, a).Result() 120 | assert.NoError(suite.T(), err1) 121 | assert.Equal(suite.T(), res1, int64(200)) 122 | } 123 | 124 | func (suite *TairStringTestSuite) TestExIncrByFloat() { 125 | suite.tairClient.ExSet(ctx, "foo", 100) 126 | result, err := suite.tairClient.ExIncrByFloat(ctx, "foo", 10.123).Result() 127 | assert.NoError(suite.T(), err) 128 | assert.InDelta(suite.T(), result, 110.123, 0.01) 129 | } 130 | 131 | func (suite *TairStringTestSuite) TestExCas() { 132 | suite.tairClient.ExSet(ctx, "foo", "bar") 133 | res2, err2 := suite.tairClient.ExCas(ctx, "foo", "bzz", 1).Result() 134 | assert.NoError(suite.T(), err2) 135 | assert.Equal(suite.T(), res2[0], "OK") 136 | assert.Equal(suite.T(), res2[1], "") 137 | assert.Equal(suite.T(), res2[2], int64(2)) 138 | 139 | res3, err3 := suite.tairClient.ExCas(ctx, "foo", "bee", 1).Result() 140 | assert.NoError(suite.T(), err3) 141 | assert.Equal(suite.T(), res3[0], "CAS_FAILED") 142 | assert.Equal(suite.T(), res3[1], "bzz") 143 | assert.Equal(suite.T(), res3[2], int64(2)) 144 | } 145 | 146 | func (suite *TairStringTestSuite) TestExCad() { 147 | suite.tairClient.ExSet(ctx, "foo", "bar") 148 | result, err := suite.tairClient.ExCad(ctx, "foo", 0).Result() 149 | assert.NoError(suite.T(), err) 150 | assert.Equal(suite.T(), result, int64(0)) 151 | 152 | result1, err1 := suite.tairClient.ExCad(ctx, "foo", 1).Result() 153 | assert.NoError(suite.T(), err1) 154 | assert.Equal(suite.T(), result1, int64(1)) 155 | } 156 | 157 | func (suite *TairStringTestSuite) TestEXAPPEND() { 158 | result, err := suite.tairClient.ExAppend(ctx, "exstringkey ", "foo", "nx", "ver", 99).Result() 159 | assert.NoError(suite.T(), err) 160 | assert.Equal(suite.T(), result, int64(1)) 161 | } 162 | 163 | func (suite *TairStringTestSuite) TestEXPREPEND() { 164 | result, err := suite.tairClient.ExPreAppend(ctx, "exstringkey ", "foo", "nx", "ver", 99).Result() 165 | assert.NoError(suite.T(), err) 166 | assert.Equal(suite.T(), result, int64(1)) 167 | } 168 | 169 | func (suite *TairStringTestSuite) TestEXGAE() { 170 | a := tair.ExSetArgs{}.New() 171 | a.Ex(10) 172 | a.Flags(123) 173 | suite.tairClient.ExSetArgs(ctx, "exstringkey", "foo", a) 174 | suite.tairClient.TTL(ctx, "exstringkey") 175 | result, err := suite.tairClient.ExGae(ctx, "exstringkey", "ex", 20).Result() 176 | assert.NoError(suite.T(), err) 177 | assert.Equal(suite.T(), result[0], "foo") 178 | assert.Equal(suite.T(), result[1], int64(1)) 179 | assert.Equal(suite.T(), result[2], int64(123)) 180 | } 181 | 182 | func TestTairStringTestSuite(t *testing.T) { 183 | suite.Run(t, new(TairStringTestSuite)) 184 | } 185 | -------------------------------------------------------------------------------- /tair/tairzset.go: -------------------------------------------------------------------------------- 1 | package tair 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/redis/go-redis/v9" 9 | ) 10 | 11 | type ExZAddArgs struct { 12 | arg 13 | xx string 14 | nx string 15 | ch string 16 | incr string 17 | } 18 | 19 | func (a ExZAddArgs) New() *ExZAddArgs { 20 | a.Set = make(map[string]bool) 21 | return &a 22 | } 23 | 24 | func (a *ExZAddArgs) Xx() *ExZAddArgs { 25 | a.Set[XX] = true 26 | return a 27 | } 28 | 29 | func (a *ExZAddArgs) Nx() *ExZAddArgs { 30 | a.Set[NX] = true 31 | return a 32 | } 33 | 34 | func (a *ExZAddArgs) Ch() *ExZAddArgs { 35 | a.Set[CH] = true 36 | return a 37 | } 38 | 39 | func (a *ExZAddArgs) Incr() *ExZAddArgs { 40 | a.Set[INCR] = true 41 | return a 42 | } 43 | 44 | func (a *ExZAddArgs) GetArgs() []interface{} { 45 | args := make([]interface{}, 0) 46 | if _, ok := a.Set[XX]; ok { 47 | args = append(args, XX) 48 | } 49 | if _, ok := a.Set[NX]; ok { 50 | args = append(args, NX) 51 | } 52 | if _, ok := a.Set[CH]; ok { 53 | args = append(args, CH) 54 | } 55 | if _, ok := a.Set[INCR]; ok { 56 | args = append(args, INCR) 57 | } 58 | return args 59 | } 60 | 61 | type ExZAddMember struct { 62 | Score string 63 | Member string 64 | } 65 | 66 | func joinScoresToString(scores ...float64) string { 67 | var builder strings.Builder 68 | for _, score := range scores { 69 | builder.WriteString(fmt.Sprintf("%f", score)) 70 | builder.WriteString("#") 71 | } 72 | strs := builder.String() 73 | return strs[:len(strs)-1] 74 | } 75 | 76 | type ExZRangeArgs struct { 77 | arg 78 | arger 79 | withScores string 80 | offset int64 81 | count int64 82 | } 83 | 84 | func (a ExZRangeArgs) New() *ExZRangeArgs { 85 | a.Set = make(map[string]bool) 86 | return &a 87 | } 88 | 89 | func (a *ExZRangeArgs) WithScores() *ExZRangeArgs { 90 | a.Set[WITHSCORES] = true 91 | return a 92 | } 93 | 94 | func (a *ExZRangeArgs) Limit(offset, count int64) *ExZRangeArgs { 95 | a.Set[LIMIT] = true 96 | a.offset = offset 97 | a.count = count 98 | return a 99 | } 100 | 101 | func (a *ExZRangeArgs) GetArgs() []interface{} { 102 | args := make([]interface{}, 0) 103 | if _, ok := a.Set[WITHSCORES]; ok { 104 | args = append(args, WITHSCORES) 105 | } 106 | if _, ok := a.Set[LIMIT]; ok { 107 | args = append(args, LIMIT, a.offset, a.count) 108 | } 109 | return args 110 | } 111 | 112 | func (tc tairCmdable) exZAdd(ctx context.Context, key string, p *ExZAddArgs, member ...ExZAddMember) *redis.IntCmd { 113 | a := make([]interface{}, 0) 114 | a = append(a, "exzadd", key) 115 | 116 | a = append(a, p.GetArgs()...) 117 | for _, m := range member { 118 | a = append(a, m.Score) 119 | a = append(a, m.Member) 120 | } 121 | cmd := redis.NewIntCmd(ctx, a...) 122 | _ = tc(ctx, cmd) 123 | return cmd 124 | 125 | } 126 | func (tc tairCmdable) ExZAddManyScore(ctx context.Context, key string, member string, scores ...float64) *redis.IntCmd { 127 | args := make([]interface{}, 4) 128 | args[0] = "exzadd" 129 | args[1] = key 130 | args[2] = joinScoresToString(scores...) 131 | args[3] = member 132 | cmd := redis.NewIntCmd(ctx, args...) 133 | _ = tc(ctx, cmd) 134 | return cmd 135 | } 136 | 137 | func (tc tairCmdable) ExZAdd(ctx context.Context, key string, score string, member string) *redis.IntCmd { 138 | cmd := tc.exZAdd(ctx, key, ExZAddArgs{}.New(), ExZAddMember{score, member}) 139 | return cmd 140 | } 141 | 142 | func (tc tairCmdable) ExZAddArgs(ctx context.Context, key string, score string, member string, a *ExZAddArgs) *redis.IntCmd { 143 | cmd := tc.exZAdd(ctx, key, a, ExZAddMember{score, member}) 144 | return cmd 145 | } 146 | 147 | func (tc tairCmdable) ExZAddManyMember(ctx context.Context, key string, member ...ExZAddMember) *redis.IntCmd { 148 | cmd := tc.exZAdd(ctx, key, ExZAddArgs{}.New(), member...) 149 | return cmd 150 | } 151 | 152 | func (tc tairCmdable) ExZAddManyMemberArgs(ctx context.Context, key string, a *ExZAddArgs, member ...ExZAddMember) *redis.IntCmd { 153 | cmd := tc.exZAdd(ctx, key, a, member...) 154 | return cmd 155 | } 156 | 157 | func (tc tairCmdable) ExZIncrBy(ctx context.Context, key string, score string, member string) *redis.StringCmd { 158 | a := make([]interface{}, 4) 159 | a[0] = "exzincrby" 160 | a[1] = key 161 | a[2] = score 162 | a[3] = member 163 | cmd := redis.NewStringCmd(ctx, a...) 164 | _ = tc(ctx, cmd) 165 | return cmd 166 | } 167 | 168 | func (tc tairCmdable) ExZIncrByManyScore(ctx context.Context, key string, member string, score ...float64) *redis.StringSliceCmd { 169 | a := make([]interface{}, 4) 170 | a[0] = "exzincryby" 171 | a[1] = key 172 | a[2] = joinScoresToString(score...) 173 | a[3] = member 174 | cmd := redis.NewStringSliceCmd(ctx, a...) 175 | _ = tc(ctx, cmd) 176 | return cmd 177 | } 178 | func (tc tairCmdable) ExZRem(ctx context.Context, key string, member ...string) *redis.IntCmd { 179 | a := make([]interface{}, 2) 180 | a[0] = "exzrem" 181 | a[1] = key 182 | for _, m := range member { // todo slice copy performance optimization 183 | a = append(a, m) 184 | } 185 | cmd := redis.NewIntCmd(ctx, a...) 186 | _ = tc(ctx, cmd) 187 | return cmd 188 | } 189 | 190 | func (tc tairCmdable) ExZRemRangeByScore(ctx context.Context, key, min, max string) *redis.IntCmd { 191 | a := make([]interface{}, 4) 192 | a[0] = "exzremrangebyscore" 193 | a[1] = key 194 | a[2] = min 195 | a[3] = max 196 | cmd := redis.NewIntCmd(ctx, a...) 197 | _ = tc(ctx, cmd) 198 | return cmd 199 | } 200 | 201 | func (tc tairCmdable) ExZRemRangeByRank(ctx context.Context, key string, start, stop int) *redis.IntCmd { 202 | a := make([]interface{}, 4) 203 | a[0] = "exzremrangebyrank" 204 | a[1] = key 205 | a[2] = start 206 | a[3] = stop 207 | cmd := redis.NewIntCmd(ctx, a...) 208 | _ = tc(ctx, cmd) 209 | return cmd 210 | } 211 | 212 | func (tc tairCmdable) ExZRemRangeByLex(ctx context.Context, key, min, max string) *redis.IntCmd { 213 | a := make([]interface{}, 4) 214 | a[0] = "exzremrangebylex" 215 | a[1] = key 216 | a[2] = min 217 | a[3] = max 218 | cmd := redis.NewIntCmd(ctx, a...) 219 | _ = tc(ctx, cmd) 220 | return cmd 221 | } 222 | 223 | func (tc tairCmdable) ExZScore(ctx context.Context, key, member string) *redis.StringCmd { 224 | a := make([]interface{}, 3) 225 | a[0] = "exzscore" 226 | a[1] = key 227 | a[2] = member 228 | cmd := redis.NewStringCmd(ctx, a...) 229 | _ = tc(ctx, cmd) 230 | return cmd 231 | } 232 | 233 | func (tc tairCmdable) ExZRange(ctx context.Context, key string, min, max int64) *redis.StringSliceCmd { 234 | a := make([]interface{}, 4) 235 | a[0] = "exzrange" 236 | a[1] = key 237 | a[2] = min 238 | a[3] = max 239 | cmd := redis.NewStringSliceCmd(ctx, a...) 240 | _ = tc(ctx, cmd) 241 | return cmd 242 | } 243 | 244 | func (tc tairCmdable) ExZRangeWithScores(ctx context.Context, key string, min, max int64) *redis.StringSliceCmd { 245 | a := make([]interface{}, 5) 246 | a[0] = "exzrange" 247 | a[1] = key 248 | a[2] = min 249 | a[3] = max 250 | a[4] = "WITHSCORES" 251 | cmd := redis.NewStringSliceCmd(ctx, a...) 252 | _ = tc(ctx, cmd) 253 | return cmd 254 | } 255 | 256 | func (tc tairCmdable) ExZRevRange(ctx context.Context, key string, min, max int) *redis.StringSliceCmd { 257 | a := make([]interface{}, 4) 258 | a[0] = "exzrevrange" 259 | a[1] = key 260 | a[2] = min 261 | a[3] = max 262 | cmd := redis.NewStringSliceCmd(ctx, a...) 263 | _ = tc(ctx, cmd) 264 | return cmd 265 | } 266 | 267 | func (tc tairCmdable) ExZRevRangeWithScores(ctx context.Context, key string, min, max int64) *redis.StringSliceCmd { 268 | a := make([]interface{}, 5) 269 | a[0] = "exzrevrange" 270 | a[1] = key 271 | a[2] = min 272 | a[3] = max 273 | a[4] = "WITHSCORES" 274 | cmd := redis.NewStringSliceCmd(ctx, a...) 275 | _ = tc(ctx, cmd) 276 | return cmd 277 | } 278 | 279 | func (tc tairCmdable) ExZRangeByScore(ctx context.Context, key, min, max string) *redis.StringSliceCmd { 280 | a := make([]interface{}, 4) 281 | a[0] = "exzrangebyscore" 282 | a[1] = key 283 | a[2] = min 284 | a[3] = max 285 | cmd := redis.NewStringSliceCmd(ctx, a...) 286 | _ = tc(ctx, cmd) 287 | return cmd 288 | } 289 | 290 | func (tc tairCmdable) ExZRangeByScoreWithArgs(ctx context.Context, key, min, max string, arg *ExZRangeArgs) *redis.StringSliceCmd { 291 | a := make([]interface{}, 4) 292 | a[0] = "exzrangebyscore" 293 | a[1] = key 294 | a[2] = min 295 | a[3] = max 296 | a = append(a, arg.GetArgs()...) 297 | cmd := redis.NewStringSliceCmd(ctx, a...) 298 | _ = tc(ctx, cmd) 299 | return cmd 300 | } 301 | 302 | func (tc tairCmdable) ExZRevRangeByScore(ctx context.Context, key, min, max string) *redis.StringSliceCmd { 303 | a := make([]interface{}, 4) 304 | a[0] = "exzrevrangebyscore" 305 | a[1] = key 306 | a[2] = min 307 | a[3] = max 308 | cmd := redis.NewStringSliceCmd(ctx, a...) 309 | _ = tc(ctx, cmd) 310 | return cmd 311 | } 312 | 313 | func (tc tairCmdable) ExZRevRangeByScoreWithArgs(ctx context.Context, key, min, max string, arg *ExZRangeArgs) *redis.StringSliceCmd { 314 | a := make([]interface{}, 4) 315 | a[0] = "exzrevrangebyscore" 316 | a[1] = key 317 | a[2] = min 318 | a[3] = max 319 | a = append(a, arg.GetArgs()...) 320 | cmd := redis.NewStringSliceCmd(ctx, a...) 321 | _ = tc(ctx, cmd) 322 | return cmd 323 | } 324 | 325 | func (tc tairCmdable) ExZRangeByLex(ctx context.Context, key, min, max string) *redis.StringSliceCmd { 326 | a := make([]interface{}, 4) 327 | a[0] = "exzrangebylex" 328 | a[1] = key 329 | a[2] = min 330 | a[3] = max 331 | cmd := redis.NewStringSliceCmd(ctx, a...) 332 | _ = tc(ctx, cmd) 333 | return cmd 334 | } 335 | 336 | func (tc tairCmdable) ExZRangeByLexWithArgs(ctx context.Context, key, min, max string, args *ExZRangeArgs) *redis.StringSliceCmd { 337 | a := make([]interface{}, 4) 338 | a[0] = "exzrangebylex" 339 | a[1] = key 340 | a[2] = min 341 | a[3] = max 342 | a = append(a, args.GetArgs()...) 343 | cmd := redis.NewStringSliceCmd(ctx, a...) 344 | _ = tc(ctx, cmd) 345 | return cmd 346 | } 347 | 348 | func (tc tairCmdable) ExZRevRangeByLex(ctx context.Context, key, min, max string) *redis.StringSliceCmd { 349 | a := make([]interface{}, 4) 350 | a[0] = "exzrevrangebylex" 351 | a[1] = key 352 | a[2] = min 353 | a[3] = max 354 | cmd := redis.NewStringSliceCmd(ctx, a...) 355 | _ = tc(ctx, cmd) 356 | return cmd 357 | } 358 | 359 | func (tc tairCmdable) ExZRevRangeByLexWithArgs(ctx context.Context, key, min, max string, arg *ExZRangeArgs) *redis.StringSliceCmd { 360 | a := make([]interface{}, 4) 361 | a[0] = "exzrevrangebylex" 362 | a[1] = key 363 | a[2] = min 364 | a[3] = max 365 | a = append(a, arg.GetArgs()...) 366 | cmd := redis.NewStringSliceCmd(ctx, a...) 367 | _ = tc(ctx, cmd) 368 | return cmd 369 | } 370 | func (tc tairCmdable) ExZCard(ctx context.Context, key string) *redis.IntCmd { 371 | cmd := redis.NewIntCmd(ctx, "exzcard", key) 372 | _ = tc(ctx, cmd) 373 | return cmd 374 | } 375 | func (tc tairCmdable) ExZRank(ctx context.Context, key, member string) *redis.IntCmd { 376 | cmd := redis.NewIntCmd(ctx, "exzrank", key, member) 377 | _ = tc(ctx, cmd) 378 | return cmd 379 | } 380 | 381 | func (tc tairCmdable) ExZRevRank(ctx context.Context, key, member string) *redis.IntCmd { 382 | cmd := redis.NewIntCmd(ctx, "exzrevrank", key, member) 383 | _ = tc(ctx, cmd) 384 | return cmd 385 | } 386 | 387 | func (tc tairCmdable) ExZRankByScore(ctx context.Context, key, score string) *redis.IntCmd { 388 | cmd := redis.NewIntCmd(ctx, "exzrankbyscore", key, score) 389 | _ = tc(ctx, cmd) 390 | return cmd 391 | } 392 | 393 | func (tc tairCmdable) ExZRevRankByScore(ctx context.Context, key, score string) *redis.IntCmd { 394 | cmd := redis.NewIntCmd(ctx, "exzrevrankbyscore", key, score) 395 | _ = tc(ctx, cmd) 396 | return cmd 397 | } 398 | 399 | func (tc tairCmdable) ExZCount(ctx context.Context, key, min, max string) *redis.IntCmd { 400 | a := make([]interface{}, 4) 401 | a[0] = "exzcount" 402 | a[1] = key 403 | a[2] = min 404 | a[3] = max 405 | cmd := redis.NewIntCmd(ctx, a...) 406 | _ = tc(ctx, cmd) 407 | return cmd 408 | } 409 | 410 | func (tc tairCmdable) ExZLexCount(ctx context.Context, key, min, max string) *redis.IntCmd { 411 | a := make([]interface{}, 4) 412 | a[0] = "exzlexcount" 413 | a[1] = key 414 | a[2] = min 415 | a[3] = max 416 | cmd := redis.NewIntCmd(ctx, a...) 417 | _ = tc(ctx, cmd) 418 | return cmd 419 | } 420 | -------------------------------------------------------------------------------- /tair/tairzset_test.go: -------------------------------------------------------------------------------- 1 | package tair_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/alibaba/tair-go/tair" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/suite" 9 | ) 10 | 11 | type TairZsetTestSuite struct { 12 | suite.Suite 13 | tairClient *tair.TairClient 14 | } 15 | 16 | func (suite *TairZsetTestSuite) SetupTest() { 17 | suite.tairClient = tair.NewTairClient(redisOptions()) 18 | assert.Equal(suite.T(), "OK", suite.tairClient.FlushDB(ctx).Val()) 19 | } 20 | 21 | func (suite *TairZsetTestSuite) TearDownTest() { 22 | assert.NoError(suite.T(), suite.tairClient.Close()) 23 | } 24 | 25 | func (suite *TairZsetTestSuite) TestExZAdd() { 26 | res, err := suite.tairClient.ExZAdd(ctx, "k1", "90.1", "v1").Result() 27 | assert.NoError(suite.T(), err) 28 | assert.Equal(suite.T(), res, int64(1)) 29 | 30 | zRangeRes, err := suite.tairClient.ExZRange(ctx, "k1", 0, -1).Result() 31 | assert.NoError(suite.T(), err) 32 | assert.Equal(suite.T(), zRangeRes[0], "v1") 33 | 34 | res, err = suite.tairClient.ExZAdd(ctx, "foo", "1", "a").Result() 35 | assert.NoError(suite.T(), err) 36 | assert.Equal(suite.T(), res, int64(1)) 37 | 38 | res, err = suite.tairClient.ExZAdd(ctx, "foo", "10", "b").Result() 39 | assert.NoError(suite.T(), err) 40 | assert.Equal(suite.T(), res, int64(1)) 41 | 42 | res, err = suite.tairClient.ExZAdd(ctx, "foo", "2", "a").Result() 43 | assert.NoError(suite.T(), err) 44 | assert.Equal(suite.T(), res, int64(0)) 45 | } 46 | 47 | func (suite *TairZsetTestSuite) TestExZAddManyScore() { 48 | res, err := suite.tairClient.ExZAddManyScore(ctx, "k1", "a", 32, 21, 16).Result() 49 | assert.NoError(suite.T(), err) 50 | assert.Equal(suite.T(), res, int64(1)) 51 | 52 | res, err = suite.tairClient.ExZAddManyScore(ctx, "k1", "d", 14, 4, 16).Result() 53 | assert.NoError(suite.T(), err) 54 | assert.Equal(suite.T(), res, int64(1)) 55 | 56 | res, err = suite.tairClient.ExZAddManyScore(ctx, "k1", "c", 20, 7, 12).Result() 57 | assert.NoError(suite.T(), err) 58 | assert.Equal(suite.T(), res, int64(1)) 59 | 60 | res2, err := suite.tairClient.ExZRange(ctx, "k1", 0, -1).Result() 61 | assert.NoError(suite.T(), err) 62 | assert.Equal(suite.T(), res2, []string{"d", "c", "a"}) 63 | } 64 | 65 | func (suite *TairZsetTestSuite) TestExZAddParams() { 66 | res, err := suite.tairClient.ExZAddArgs(ctx, "foo", "1", "a", tair.ExZAddArgs{}.New().Xx()).Result() 67 | assert.NoError(suite.T(), err) 68 | assert.Equal(suite.T(), res, int64(0)) 69 | 70 | res, err = suite.tairClient.ExZAdd(ctx, "foo", "1", "a").Result() 71 | assert.NoError(suite.T(), err) 72 | assert.Equal(suite.T(), res, int64(1)) 73 | 74 | res, err = suite.tairClient.ExZAddArgs(ctx, "foo", "2", "a", tair.ExZAddArgs{}.New().Nx()).Result() 75 | assert.NoError(suite.T(), err) 76 | assert.Equal(suite.T(), res, int64(0)) 77 | 78 | res, err = suite.tairClient.ExZAddManyMemberArgs(ctx, "foo", tair.ExZAddArgs{}.New().Ch(), 79 | tair.ExZAddMember{Score: "2", Member: "a"}, tair.ExZAddMember{Score: "1", Member: "b"}).Result() 80 | assert.NoError(suite.T(), err) 81 | assert.Equal(suite.T(), res, int64(2)) 82 | } 83 | 84 | func (suite *TairZsetTestSuite) TestExZRangeBasic() { 85 | res, err := suite.tairClient.ExZAddManyMember(ctx, "foo", 86 | tair.ExZAddMember{Score: "1", Member: "a"}, tair.ExZAddMember{Score: "10", Member: "b"}, 87 | tair.ExZAddMember{Score: "0.1", Member: "c"}, tair.ExZAddMember{Score: "2", Member: "a"}).Result() 88 | assert.NoError(suite.T(), err) 89 | assert.Equal(suite.T(), res, int64(3)) 90 | 91 | ss, err := suite.tairClient.ExZRange(ctx, "foo", 0, 1).Result() 92 | assert.NoError(suite.T(), err) 93 | assert.Equal(suite.T(), ss, []string{"c", "a"}) 94 | 95 | ss, err = suite.tairClient.ExZRange(ctx, "foo", 0, -1).Result() 96 | assert.NoError(suite.T(), err) 97 | assert.Equal(suite.T(), ss, []string{"c", "a", "b"}) 98 | 99 | ss, err = suite.tairClient.ExZRevRange(ctx, "foo", 0, 1).Result() 100 | assert.NoError(suite.T(), err) 101 | assert.Equal(suite.T(), ss, []string{"b", "a"}) 102 | } 103 | 104 | func (suite *TairZsetTestSuite) TestExZRangeByLex() { 105 | res, err := suite.tairClient.ExZAddManyMember(ctx, "foo", 106 | tair.ExZAddMember{Score: "1", Member: "aa"}, tair.ExZAddMember{Score: "1", Member: "c"}, 107 | tair.ExZAddMember{Score: "1", Member: "bb"}, tair.ExZAddMember{Score: "1", Member: "d"}).Result() 108 | assert.NoError(suite.T(), err) 109 | assert.Equal(suite.T(), res, int64(4)) 110 | 111 | ss, err := suite.tairClient.ExZRangeByLex(ctx, "foo", "(aa", "[c").Result() 112 | assert.NoError(suite.T(), err) 113 | assert.Equal(suite.T(), ss, []string{"bb", "c"}) 114 | 115 | ss, err = suite.tairClient.ExZRangeByLexWithArgs(ctx, "foo", "-", "+", tair.ExZRangeArgs{}.New().Limit(1, 2)).Result() 116 | assert.NoError(suite.T(), err) 117 | assert.Equal(suite.T(), ss, []string{"bb", "c"}) 118 | } 119 | 120 | func (suite *TairZsetTestSuite) TestExZRemBasic() { 121 | res, err := suite.tairClient.ExZAddManyMember(ctx, "foo", 122 | tair.ExZAddMember{Score: "1", Member: "a"}, tair.ExZAddMember{Score: "2", Member: "b"}).Result() 123 | assert.NoError(suite.T(), err) 124 | assert.Equal(suite.T(), res, int64(2)) 125 | 126 | is, err := suite.tairClient.ExZRem(ctx, "foo", "a").Result() 127 | assert.NoError(suite.T(), err) 128 | assert.Equal(suite.T(), is, int64(1)) 129 | } 130 | 131 | func (suite *TairZsetTestSuite) TestExZIncrbyBacis() { 132 | res, err := suite.tairClient.ExZAddManyMember(ctx, "foo", 133 | tair.ExZAddMember{Score: "1", Member: "a"}, tair.ExZAddMember{Score: "2", Member: "b"}).Result() 134 | assert.NoError(suite.T(), err) 135 | assert.Equal(suite.T(), res, int64(2)) 136 | 137 | ss, err := suite.tairClient.ExZIncrBy(ctx, "foo", "2", "a").Result() 138 | assert.NoError(suite.T(), err) 139 | assert.Equal(suite.T(), ss, "3") 140 | } 141 | 142 | func (suite *TairZsetTestSuite) TestExZrankBasic() { 143 | res, err := suite.tairClient.ExZAddManyMember(ctx, "foo", 144 | tair.ExZAddMember{Score: "1", Member: "a"}, tair.ExZAddMember{Score: "2", Member: "b"}).Result() 145 | assert.NoError(suite.T(), err) 146 | assert.Equal(suite.T(), res, int64(2)) 147 | 148 | res, err = suite.tairClient.ExZRank(ctx, "foo", "a").Result() 149 | assert.NoError(suite.T(), err) 150 | assert.Equal(suite.T(), res, int64(0)) 151 | 152 | res, err = suite.tairClient.ExZRank(ctx, "foo", "b").Result() 153 | assert.NoError(suite.T(), err) 154 | assert.Equal(suite.T(), res, int64(1)) 155 | 156 | res, err = suite.tairClient.ExZRevRank(ctx, "foo", "a").Result() 157 | assert.NoError(suite.T(), err) 158 | assert.Equal(suite.T(), res, int64(1)) 159 | } 160 | 161 | func (suite *TairZsetTestSuite) TestExZRangeWithScorBasic() { 162 | res, err := suite.tairClient.ExZAddManyMember(ctx, "foo", 163 | tair.ExZAddMember{Score: "1", Member: "a"}, tair.ExZAddMember{Score: "10", Member: "b"}, 164 | tair.ExZAddMember{Score: "0.1", Member: "c"}, tair.ExZAddMember{Score: "2", Member: "a"}).Result() 165 | assert.NoError(suite.T(), err) 166 | assert.Equal(suite.T(), res, int64(3)) 167 | 168 | ss, err := suite.tairClient.ExZRangeWithScores(ctx, "foo", 0, 1).Result() 169 | assert.NoError(suite.T(), err) 170 | assert.Equal(suite.T(), ss, []string{"c", "0.10000000000000001", "a", "2"}) 171 | } 172 | 173 | func (suite *TairZsetTestSuite) TestExZcardBasic() { 174 | res, err := suite.tairClient.ExZAddManyMember(ctx, "foo", 175 | tair.ExZAddMember{Score: "1", Member: "a"}, tair.ExZAddMember{Score: "10", Member: "b"}, 176 | tair.ExZAddMember{Score: "0.1", Member: "c"}, tair.ExZAddMember{Score: "2", Member: "a"}).Result() 177 | assert.NoError(suite.T(), err) 178 | assert.Equal(suite.T(), res, int64(3)) 179 | 180 | res, err = suite.tairClient.ExZCard(ctx, "foo").Result() 181 | assert.NoError(suite.T(), err) 182 | assert.Equal(suite.T(), res, int64(3)) 183 | 184 | ss, err := suite.tairClient.ExZScore(ctx, "foo", "b").Result() 185 | assert.NoError(suite.T(), err) 186 | assert.Equal(suite.T(), ss, "10") 187 | } 188 | 189 | func (suite *TairZsetTestSuite) TestExZCountBasic() { 190 | res, err := suite.tairClient.ExZAddManyMember(ctx, "foo", 191 | tair.ExZAddMember{Score: "1", Member: "a"}, tair.ExZAddMember{Score: "10", Member: "b"}, 192 | tair.ExZAddMember{Score: "0.1", Member: "c"}, tair.ExZAddMember{Score: "2", Member: "a"}).Result() 193 | assert.NoError(suite.T(), err) 194 | assert.Equal(suite.T(), res, int64(3)) 195 | 196 | res, err = suite.tairClient.ExZCount(ctx, "foo", "0.01", "2.1").Result() 197 | assert.NoError(suite.T(), err) 198 | assert.Equal(suite.T(), res, int64(2)) 199 | } 200 | 201 | func (suite *TairZsetTestSuite) TestExZRemRangeByRank() { 202 | res, err := suite.tairClient.ExZAddManyMember(ctx, "foo", 203 | tair.ExZAddMember{Score: "1", Member: "a"}, tair.ExZAddMember{Score: "10", Member: "b"}, 204 | tair.ExZAddMember{Score: "0.1", Member: "c"}, tair.ExZAddMember{Score: "2", Member: "a"}).Result() 205 | assert.NoError(suite.T(), err) 206 | assert.Equal(suite.T(), res, int64(3)) 207 | 208 | res, err = suite.tairClient.ExZRemRangeByRank(ctx, "foo", 0, 0).Result() 209 | assert.NoError(suite.T(), err) 210 | assert.Equal(suite.T(), res, int64(1)) 211 | 212 | ss, err := suite.tairClient.ExZRange(ctx, "foo", 0, -1).Result() 213 | assert.NoError(suite.T(), err) 214 | assert.Equal(suite.T(), ss, []string{"a", "b"}) 215 | } 216 | 217 | func TestTairZsetTestSuite(t *testing.T) { 218 | suite.Run(t, new(TairZsetTestSuite)) 219 | } 220 | --------------------------------------------------------------------------------