├── README.md ├── .githooks ├── pre-commit └── README.md ├── .gitignore ├── moon.pkg.json ├── redis_test.mbt ├── moon.mod.json ├── README.mbt.md ├── subscribe_test.mbt ├── redis.mbt ├── hash.mbt ├── Agents.md ├── .trae └── rules │ └── project_rules.md ├── string_test.mbt ├── list_test.mbt ├── set_test.mbt ├── set.mbt ├── bitmap.mbt ├── connection.mbt ├── subscribe.mbt ├── hash_test.mbt ├── sorted_set_test.mbt ├── list.mbt ├── json.mbt ├── string.mbt ├── geo.mbt ├── cluster.mbt ├── generic.mbt ├── server.mbt ├── stream.mbt ├── sorted_set.mbt ├── json_test.mbt ├── LICENSE ├── types.mbt ├── stream_test.mbt └── pkg.generated.mbti /README.md: -------------------------------------------------------------------------------- 1 | README.mbt.md -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | moon check -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | target/ 3 | .mooncakes/ 4 | .moonagent/ 5 | commands/ -------------------------------------------------------------------------------- /moon.pkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "import": [ 3 | "moonbitlang/async/socket", 4 | "moonbitlang/async/io" 5 | ], 6 | "test-import": [ 7 | "moonbitlang/async" 8 | ] 9 | } -------------------------------------------------------------------------------- /redis_test.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | async test "ping" { 3 | @async.with_task_group(fn(_root) { 4 | let client = @redis.connect("localhost", 6379) 5 | defer client.close() 6 | let pong = client.ping() 7 | assert_true(pong is Ok("PONG")) 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /moon.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oboard/redis", 3 | "version": "0.1.1", 4 | "deps": { 5 | "moonbitlang/async": "0.8.1" 6 | }, 7 | "readme": "README.mbt.md", 8 | "repository": "https://github.com/oboard/moonbit-redis", 9 | "license": "Apache-2.0", 10 | "keywords": [ 11 | "redis", 12 | "client", 13 | "valkey" 14 | ], 15 | "description": "A modern, high performance Redis client for MoonBit.", 16 | "preferred-target": "native" 17 | } -------------------------------------------------------------------------------- /.githooks/README.md: -------------------------------------------------------------------------------- 1 | # Git Hooks 2 | 3 | ## Pre-commit Hook 4 | 5 | This pre-commit hook performs automatic checks before finalizing your commit. 6 | 7 | ### Usage Instructions 8 | 9 | To use this pre-commit hook: 10 | 11 | 1. Make the hook executable if it isn't already: 12 | ```bash 13 | chmod +x .githooks/pre-commit 14 | ``` 15 | 16 | 2. Configure Git to use the hooks in the .githooks directory: 17 | ```bash 18 | git config core.hooksPath .githooks 19 | ``` 20 | 21 | 3. The hook will automatically run when you execute `git commit` 22 | -------------------------------------------------------------------------------- /README.mbt.md: -------------------------------------------------------------------------------- 1 | # oboard/redis 2 | moonbit-redis is a modern, high performance Redis client for MoonBit. 3 | 4 | ## Usage 5 | ### Basic Example 6 | ```moonbit 7 | ///| 8 | async test "string_set_get" { 9 | @async.with_task_group(fn(_root) { 10 | let client = connect("localhost", 6379) 11 | defer client.close() 12 | 13 | // SET 14 | let set_result = client.set("test:string", "hello_world") 15 | assert_true(set_result is Ok("OK")) 16 | 17 | // GET 18 | let get_result = client.get("test:string") 19 | assert_true(get_result is Ok("hello_world")) 20 | 21 | // Cleanup 22 | let _ = client.del(["test:string"]) 23 | 24 | }) 25 | } 26 | ``` 27 | 28 | ### Ping 29 | ```moonbit 30 | ///| 31 | async test "ping" { 32 | @async.with_task_group(fn(_root) { 33 | let client = @redis.connect("localhost", 6379) 34 | defer client.close() 35 | let pong = client.ping() 36 | assert_true(pong is Ok("PONG")) 37 | }) 38 | } 39 | ``` -------------------------------------------------------------------------------- /subscribe_test.mbt: -------------------------------------------------------------------------------- 1 | /// Redis Subscribe/Unsubscribe functionality tests 2 | 3 | ///| 4 | /// Test publish command 5 | async test "publish command" { 6 | @async.with_task_group(fn(_root) { 7 | let client = connect("localhost", 6379) 8 | defer client.close() 9 | 10 | // Test publish to a channel 11 | let subscriber_count = client 12 | .publish("test_channel", "hello world") 13 | .unwrap() 14 | assert_true(subscriber_count >= 0) 15 | }) 16 | } 17 | 18 | ///| 19 | async test "complex subscription scenario" { 20 | @async.with_task_group(fn(_root) { 21 | let client1 = connect("localhost", 6379) 22 | let client2 = connect("localhost", 6379) 23 | let client3 = connect("localhost", 6379) 24 | 25 | // Client 1: Subscribe to regular channels 26 | let _ = client1.subscribe(["news", "sports"]) 27 | 28 | // Client 2: Subscribe to patterns 29 | let _ = client2.psubscribe(["news.*", "sports.*"]) 30 | 31 | // Client 3: Publish messages 32 | let news_subscribers = client3.publish("news", "Breaking news!").unwrap() 33 | let sports_subscribers = client3.publish("sports", "Game results").unwrap() 34 | 35 | // Verify subscribers received messages (at least 1 each) 36 | assert_true(news_subscribers >= 1) 37 | assert_true(sports_subscribers >= 1) 38 | 39 | // Clean up subscriptions 40 | let _ = client1.unsubscribe() 41 | let _ = client2.punsubscribe() 42 | 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /redis.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | pub struct RedisClient { 3 | conn : @socket.TCP 4 | } 5 | 6 | ///| 7 | pub async fn connect(host : String, port : Int) -> RedisClient { 8 | let conn = @socket.TCP::connect_to_host(host, port~) 9 | conn.enable_keepalive() 10 | { conn, } 11 | } 12 | 13 | ///| 14 | pub async fn RedisClient::send( 15 | self : RedisClient, 16 | args : Array[String], 17 | ) -> RedisValue { 18 | // println(args) 19 | let buf = @buffer.new() 20 | buf.write_string("*" + args.length().to_string() + "\r\n") 21 | for arg in args { 22 | buf.write_string("$\{arg.length()}\r\n\{arg}\r\n") 23 | } 24 | self.conn.write(buf.to_string()) 25 | self.read_redis_value() 26 | } 27 | 28 | ///| 29 | pub async fn RedisClient::read_response(self : RedisClient) -> String { 30 | let buf = FixedArray::make(4096, b'0') 31 | if self.conn.read(buf) is n && n > 0 { 32 | let response_bytes = buf.unsafe_reinterpret_as_bytes()[0:n] 33 | @encoding/utf8.decode(response_bytes) 34 | } else { 35 | "" 36 | } 37 | } 38 | 39 | ///| 40 | /// 读取Redis响应并解析为RedisValue类型 41 | pub async fn RedisClient::read_redis_value(self : RedisClient) -> RedisValue { 42 | let raw_response = self.read_response() 43 | // println(raw_response) 44 | parse_redis_response(raw_response) 45 | } 46 | 47 | ///| 48 | pub async fn RedisClient::write(self : RedisClient, data : String) -> Unit { 49 | self.conn.write(data) 50 | } 51 | 52 | ///| 53 | pub async fn RedisClient::read( 54 | self : RedisClient, 55 | buf : FixedArray[Byte], 56 | ) -> Int { 57 | self.conn.read(buf) 58 | } 59 | 60 | ///| 61 | pub fn RedisClient::close(self : RedisClient) -> Unit { 62 | self.conn.close() 63 | } 64 | -------------------------------------------------------------------------------- /hash.mbt: -------------------------------------------------------------------------------- 1 | // ============ 哈希命令 ============ 2 | 3 | ///| 4 | pub async fn RedisClient::hset( 5 | self : RedisClient, 6 | key : String, 7 | field : String, 8 | value : String, 9 | ) -> RedisInt { 10 | self.send(["HSET", key, field, value]).to_int() 11 | } 12 | 13 | ///| 14 | pub async fn RedisClient::hget( 15 | self : RedisClient, 16 | key : String, 17 | field : String, 18 | ) -> RedisString { 19 | self.send(["HGET", key, field]).to_string() 20 | } 21 | 22 | ///| 23 | pub async fn RedisClient::hdel( 24 | self : RedisClient, 25 | key : String, 26 | fields : Array[String], 27 | ) -> RedisInt { 28 | let args = ["HDEL", key, ..fields] 29 | self.send(args).to_int() 30 | } 31 | 32 | ///| 33 | pub async fn RedisClient::hkeys(self : RedisClient, key : String) -> RedisArray { 34 | self.send(["HKEYS", key]).to_array() 35 | } 36 | 37 | ///| 38 | pub async fn RedisClient::hvals(self : RedisClient, key : String) -> RedisArray { 39 | self.send(["HVALS", key]).to_array() 40 | } 41 | 42 | ///| 43 | pub async fn RedisClient::hgetall( 44 | self : RedisClient, 45 | key : String, 46 | ) -> RedisHash { 47 | let redis_value = self.send(["HGETALL", key]) 48 | match redis_value.to_array() { 49 | Ok(arr) => { 50 | // 将平坦数组转换为键值对 51 | let pairs = Map::new() 52 | let mut i = 0 53 | while i < arr.length() - 1 { 54 | pairs.set(arr[i], arr[i + 1]) 55 | i = i + 2 56 | } 57 | Ok(pairs) 58 | } 59 | Err(e) => Err(e) 60 | } 61 | } 62 | 63 | ///| 64 | pub async fn RedisClient::hexists( 65 | self : RedisClient, 66 | key : String, 67 | field : String, 68 | ) -> RedisBool { 69 | self.send(["HEXISTS", key, field]).to_bool() 70 | } 71 | 72 | ///| 73 | pub async fn RedisClient::hlen(self : RedisClient, key : String) -> RedisInt { 74 | self.send(["HLEN", key]).to_int() 75 | } 76 | -------------------------------------------------------------------------------- /Agents.md: -------------------------------------------------------------------------------- 1 | # Project Agents.md Guide 2 | 3 | This is a [MoonBit](https://docs.moonbitlang.com) project. 4 | 5 | ## Project Structure 6 | 7 | - MoonBit packages are organized per directory, for each directory, there is a 8 | `moon.pkg.json` file listing its dependencies. Each package has its files and 9 | blackbox test files (common, ending in `_test.mbt`) and whitebox test files 10 | (ending in `_wbtest.mbt`). 11 | 12 | - In the toplevel directory, this is a `moon.mod.json` file listing about the 13 | module and some meta information. 14 | 15 | ## Coding convention 16 | 17 | - MoonBit code is organized in block style, each block is separated by `///|`, 18 | the order of each block is irrelevant. In some refactorings, you can process 19 | block by block independently. 20 | 21 | - Try to keep deprecated blocks in file called `deprecated.mbt` in each 22 | directory. 23 | 24 | ## Tooling 25 | 26 | - `moon fmt` is used to format your code properly. 27 | 28 | - `moon info` is used to update the generated interface of the package, each 29 | package has a generated interface file `.mbti`, it is a brief formal 30 | description of the package. If nothing in `.mbti` changes, this means your 31 | change does not bring the visible changes to the external package users, it is 32 | typically a safe refactoring. 33 | 34 | - In the last step, run `moon info && moon fmt` to update the interface and 35 | format the code. Check the diffs of `.mbti` file to see if the changes are 36 | expected. 37 | 38 | - Run `moon test` to check the test is passed. MoonBit supports snapshot 39 | testing, so when your changes indeed change the behavior of the code, you 40 | should run `moon test --update` to update the snapshot. 41 | 42 | - You can run `moon check` to check the code is linted correctly. 43 | 44 | - When writing tests, you are encouraged to use `inspect` and run 45 | `moon test --update` to update the snapshots, only use assertions like 46 | `assert_eq` when you are in some loops where each snapshot may vary. You can 47 | use `moon coverage analyze > uncovered.log` to see which parts of your code 48 | are not covered by tests. 49 | 50 | - agent-todo.md has some small tasks that are easy for AI to pick up, agent is 51 | welcome to finish the tasks and check the box when you are done 52 | -------------------------------------------------------------------------------- /.trae/rules/project_rules.md: -------------------------------------------------------------------------------- 1 | 目前只支持native,所以命令要带上--target native 2 | 3 | moon check --target native 4 | 5 | moon build --target native 6 | 7 | # Project Agents.md Guide 8 | 9 | This is a [MoonBit](https://docs.moonbitlang.com) project. 10 | 11 | ## Project Structure 12 | 13 | - MoonBit packages are organized per directory, for each directory, there is a 14 | `moon.pkg.json` file listing its dependencies. Each package has its files and 15 | blackbox test files (common, ending in `_test.mbt`) and whitebox test files 16 | (ending in `_wbtest.mbt`). 17 | 18 | - In the toplevel directory, this is a `moon.mod.json` file listing about the 19 | module and some meta information. 20 | 21 | ## Coding convention 22 | 23 | - MoonBit code is organized in block style, each block is separated by `///|`, 24 | the order of each block is irrelevant. In some refactorings, you can process 25 | block by block independently. 26 | 27 | - Try to keep deprecated blocks in file called `deprecated.mbt` in each 28 | directory. 29 | 30 | ## Tooling 31 | 32 | - `moon fmt` is used to format your code properly. 33 | 34 | - `moon info` is used to update the generated interface of the package, each 35 | package has a generated interface file `.mbti`, it is a brief formal 36 | description of the package. If nothing in `.mbti` changes, this means your 37 | change does not bring the visible changes to the external package users, it is 38 | typically a safe refactoring. 39 | 40 | - In the last step, run `moon info && moon fmt` to update the interface and 41 | format the code. Check the diffs of `.mbti` file to see if the changes are 42 | expected. 43 | 44 | - Run `moon test` to check the test is passed. MoonBit supports snapshot 45 | testing, so when your changes indeed change the behavior of the code, you 46 | should run `moon test --update` to update the snapshot. 47 | 48 | - You can run `moon check` to check the code is linted correctly. 49 | 50 | - When writing tests, you are encouraged to use `inspect` and run 51 | `moon test --update` to update the snapshots, only use assertions like 52 | `assert_eq` when you are in some loops where each snapshot may vary. You can 53 | use `moon coverage analyze > uncovered.log` to see which parts of your code 54 | are not covered by tests. 55 | 56 | - agent-todo.md has some small tasks that are easy for AI to pick up, agent is 57 | welcome to finish the tasks and check the box when you are done 58 | -------------------------------------------------------------------------------- /string_test.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | /// String命令测试套件 3 | async test "string_ping" { 4 | @async.with_task_group(fn(_root) { 5 | let client = connect("localhost", 6379) 6 | defer client.close() 7 | let pong = client.ping() 8 | assert_true(pong is Ok("PONG")) 9 | }) 10 | } 11 | 12 | ///| 13 | /// 测试SET和GET命令 14 | async test "string_set_get" { 15 | @async.with_task_group(fn(_root) { 16 | let client = connect("localhost", 6379) 17 | defer client.close() 18 | 19 | // 测试SET 20 | let set_result = client.set("test:string", "hello_world") 21 | assert_true(set_result is Ok("OK")) 22 | 23 | // 测试GET 24 | let get_result = client.get("test:string") 25 | assert_true(get_result is Ok("hello_world")) 26 | 27 | // 清理 28 | let _ = client.del(["test:string"]) 29 | 30 | }) 31 | } 32 | 33 | ///| 34 | /// 测试APPEND命令 35 | async test "string_append" { 36 | @async.with_task_group(fn(_root) { 37 | let client = connect("localhost", 6379) 38 | defer client.close() 39 | 40 | // 设置初始值 41 | let _ = client.set("test:string", "hello") 42 | 43 | // 测试APPEND 44 | let append_result = client.append("test:string", "_world") 45 | assert_true(append_result is Ok(11)) // "hello_world"的长度 46 | 47 | // 验证结果 48 | let get_result = client.get("test:string") 49 | assert_true(get_result is Ok("hello_world")) 50 | 51 | // 清理 52 | let _ = client.del(["test:string"]) 53 | 54 | }) 55 | } 56 | 57 | ///| 58 | /// 测试INCR和DECR命令 59 | async test "string_incr_decr" { 60 | @async.with_task_group(fn(_root) { 61 | let client = connect("localhost", 6379) 62 | defer client.close() 63 | 64 | // 设置初始值 65 | let _ = client.set("test:counter", "10") 66 | 67 | // 测试INCR 68 | let incr_result = client.incr("test:counter") 69 | assert_true(incr_result is Ok(11)) 70 | 71 | // 测试DECR 72 | let decr_result = client.decr("test:counter") 73 | assert_true(decr_result is Ok(10)) 74 | 75 | // 清理 76 | let _ = client.del(["test:counter"]) 77 | 78 | }) 79 | } 80 | 81 | ///| 82 | /// 测试STRLEN命令 83 | async test "string_strlen" { 84 | @async.with_task_group(fn(_root) { 85 | let client = connect("localhost", 6379) 86 | defer client.close() 87 | 88 | // 设置测试值 89 | let _ = client.set("test:string", "hello") 90 | 91 | // 测试STRLEN 92 | let strlen_result = client.strlen("test:string") 93 | assert_true(strlen_result is Ok(5)) 94 | 95 | // 清理 96 | let _ = client.del(["test:string"]) 97 | 98 | }) 99 | } 100 | 101 | ///| 102 | /// 测试MSET和MGET命令 103 | async test "string_mset_mget" { 104 | @async.with_task_group(fn(_root) { 105 | let client = connect("localhost", 6379) 106 | defer client.close() 107 | 108 | // 测试MSET 109 | let mset_result = client.mset({ "key1": "value1", "key2": "value2" }) 110 | assert_true(mset_result is Ok("OK")) 111 | 112 | // 测试MGET 113 | let mget_result = client.mget(["key1", "key2"]) 114 | assert_true(mget_result is Ok(["value1", "value2"])) 115 | 116 | // 清理 117 | let _ = client.del(["key1", "key2"]) 118 | 119 | }) 120 | } 121 | -------------------------------------------------------------------------------- /list_test.mbt: -------------------------------------------------------------------------------- 1 | ///| List命令测试套件 2 | 3 | ///| 4 | /// 测试LPUSH和RPUSH命令 5 | async test "list_push" { 6 | @async.with_task_group(fn(_root) { 7 | let client = connect("localhost", 6379) 8 | defer client.close() 9 | 10 | // 测试LPUSH 11 | let lpush_result = client.lpush("test:list", ["item1", "item2"]) 12 | assert_true(lpush_result is Ok(2)) 13 | 14 | // 测试RPUSH 15 | let rpush_result = client.rpush("test:list", ["item3", "item4"]) 16 | assert_true(rpush_result is Ok(4)) 17 | 18 | // 清理 19 | let _ = client.del(["test:list"]) 20 | 21 | }) 22 | } 23 | 24 | ///| 25 | /// 测试LPOP和RPOP命令 26 | async test "list_pop" { 27 | @async.with_task_group(fn(_root) { 28 | let client = connect("localhost", 6379) 29 | defer client.close() 30 | 31 | // 准备数据 32 | let _ = client.rpush("test:list", ["item1", "item2", "item3"]) 33 | 34 | // 测试LPOP 35 | let lpop_result = client.lpop("test:list") 36 | inspect(lpop_result is Ok("item1"), content="true") 37 | 38 | // 测试RPOP 39 | let rpop_result = client.rpop("test:list") 40 | inspect(rpop_result is Ok("item3"), content="true") 41 | 42 | // 清理 43 | let _ = client.del(["test:list"]) 44 | 45 | }) 46 | } 47 | 48 | ///| 49 | /// 测试LLEN命令 50 | async test "list_llen" { 51 | @async.with_task_group(fn(_root) { 52 | let client = connect("localhost", 6379) 53 | defer client.close() 54 | 55 | // 清理 56 | let _ = client.del(["test:list"]) 57 | 58 | // 准备数据 59 | let _ = client.rpush("test:list", ["item1", "item2", "item3"]) 60 | 61 | // 测试LLEN 62 | let llen_result = client.llen("test:list") 63 | assert_true(llen_result is Ok(3)) 64 | 65 | // 清理 66 | let _ = client.del(["test:list"]) 67 | 68 | }) 69 | } 70 | 71 | ///| 72 | /// 测试LRANGE命令 73 | async test "list_lrange" { 74 | @async.with_task_group(fn(_root) { 75 | let client = connect("localhost", 6379) 76 | defer client.close() 77 | 78 | // 清理 79 | let _ = client.del(["test:list"]) 80 | // 准备数据 81 | let _ = client.rpush("test:list", ["item1", "item2", "item3"]) 82 | 83 | // 测试LRANGE 84 | let lrange_result = client.lrange("test:list", start=0, stop=-1) 85 | assert_true(lrange_result is Ok(["item1", "item2", "item3"])) 86 | 87 | // 清理 88 | let _ = client.del(["test:list"]) 89 | 90 | }) 91 | } 92 | 93 | ///| 94 | /// 测试LINDEX命令 95 | async test "list_lindex" { 96 | @async.with_task_group(fn(_root) { 97 | let client = connect("localhost", 6379) 98 | defer client.close() 99 | 100 | // 准备数据 101 | let _ = client.rpush("test:list", ["item1", "item2", "item3"]) 102 | 103 | // 测试LINDEX 104 | let lindex_result = client.lindex("test:list", index=1) 105 | assert_true(lindex_result is Ok("item2")) 106 | 107 | // 清理 108 | let _ = client.del(["test:list"]) 109 | 110 | }) 111 | } 112 | 113 | ///| 114 | /// 测试LREM命令 115 | async test "list_lrem" { 116 | @async.with_task_group(fn(_root) { 117 | let client = connect("localhost", 6379) 118 | defer client.close() 119 | 120 | // 准备数据 121 | let _ = client.rpush("test:list", ["item1", "item2", "item1", "item3"]) 122 | 123 | // 测试LREM - 移除所有"item1" 124 | let lrem_result = client.lrem("test:list", count=0, value="item1") 125 | assert_true(lrem_result is Ok(2)) 126 | 127 | // 验证剩余长度 128 | let llen_result = client.llen("test:list") 129 | assert_true(llen_result is Ok(2)) 130 | 131 | // 清理 132 | let _ = client.del(["test:list"]) 133 | 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /set_test.mbt: -------------------------------------------------------------------------------- 1 | ///| Set命令测试套件 2 | 3 | ///| 4 | /// 测试SADD和SREM命令 5 | async test "set_sadd_srem" { 6 | @async.with_task_group(fn(_root) { 7 | let client = connect("localhost", 6379) 8 | defer client.close() 9 | 10 | // 测试SADD 11 | let sadd_result = client.sadd("test:set", ["member1", "member2", "member3"]) 12 | assert_true(sadd_result is Ok(3)) 13 | 14 | // 测试SREM 15 | let srem_result = client.srem("test:set", ["member1"]) 16 | assert_true(srem_result is Ok(1)) 17 | 18 | // 清理 19 | let _ = client.del(["test:set"]) 20 | 21 | }) 22 | } 23 | 24 | ///| 25 | /// 测试SCARD命令 26 | async test "set_scard" { 27 | @async.with_task_group(fn(_root) { 28 | let client = connect("localhost", 6379) 29 | defer client.close() 30 | 31 | // 准备数据 32 | let _ = client.sadd("test:set", ["member1", "member2", "member3"]) 33 | 34 | // 测试SCARD 35 | let scard_result = client.scard("test:set") 36 | inspect(scard_result is Ok(3), content="true") 37 | 38 | // 清理 39 | let _ = client.del(["test:set"]) 40 | 41 | }) 42 | } 43 | 44 | ///| 45 | /// 测试SISMEMBER命令 46 | async test "set_sismember" { 47 | @async.with_task_group(fn(_root) { 48 | let client = connect("localhost", 6379) 49 | defer client.close() 50 | 51 | // 准备数据 52 | let _ = client.sadd("test:set", ["member1", "member2", "member3"]) 53 | 54 | // 测试SISMEMBER - 存在的成员 55 | let sismember_result1 = client.sismember("test:set", "member1") 56 | inspect(sismember_result1 is Ok(1), content="true") 57 | 58 | // 测试SISMEMBER - 不存在的成员 59 | let sismember_result2 = client.sismember("test:set", "member4") 60 | inspect(sismember_result2 is Ok(0), content="true") 61 | 62 | // 清理 63 | let _ = client.del(["test:set"]) 64 | 65 | }) 66 | } 67 | 68 | ///| 69 | /// 测试SMEMBERS命令 70 | async test "set_smembers" { 71 | @async.with_task_group(fn(_root) { 72 | let client = connect("localhost", 6379) 73 | defer client.close() 74 | 75 | // 准备数据 76 | let _ = client.sadd("test:set", ["member1", "member2", "member3"]) 77 | 78 | // 测试SMEMBERS 79 | let smembers_result = client.smembers("test:set") 80 | assert_true(smembers_result is Ok(["member1", "member2", "member3"])) 81 | 82 | // 清理 83 | let _ = client.del(["test:set"]) 84 | 85 | }) 86 | } 87 | 88 | ///| 89 | /// 测试SPOP命令 90 | async test "set_spop" { 91 | @async.with_task_group(fn(_root) { 92 | let client = connect("localhost", 6379) 93 | defer client.close() 94 | 95 | // 准备数据 96 | let _ = client.sadd("test:set", ["member1", "member2", "member3"]) 97 | 98 | // 测试SPOP 99 | let spop_result = client.spop("test:set") 100 | // 验证返回了一个成员(具体是哪个不确定) 101 | assert_true(spop_result is Ok(_)) 102 | 103 | // 验证集合大小减少了 104 | let scard_result = client.scard("test:set") 105 | assert_true(scard_result is Ok(2)) 106 | 107 | // 清理 108 | let _ = client.del(["test:set"]) 109 | 110 | }) 111 | } 112 | 113 | ///| 114 | /// 测试SRANDMEMBER命令 115 | async test "set_srandmember" { 116 | @async.with_task_group(fn(_root) { 117 | let client = connect("localhost", 6379) 118 | defer client.close() 119 | 120 | // 清理 121 | let _ = client.del(["test:set"]) 122 | // 准备数据 123 | let _ = client.sadd("test:set", ["member1", "member2", "member3"]) 124 | 125 | // 测试SRANDMEMBER 126 | let srandmember_result = client.srandmember("test:set") 127 | // 验证返回了一个成员 128 | assert_true(srandmember_result is Ok(_)) 129 | 130 | // 验证集合大小没有变化 131 | let scard_result = client.scard("test:set") 132 | assert_true(scard_result is Ok(3)) 133 | 134 | // 清理 135 | let _ = client.del(["test:set"]) 136 | 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /set.mbt: -------------------------------------------------------------------------------- 1 | // ============ 集合命令 ============ 2 | 3 | ///| 4 | pub async fn RedisClient::sadd( 5 | self : RedisClient, 6 | key : String, 7 | members : Array[String], 8 | ) -> RedisInt { 9 | let args = ["SADD", key, ..members] 10 | self.send(args).to_int() 11 | } 12 | 13 | ///| 14 | pub async fn RedisClient::srem( 15 | self : RedisClient, 16 | key : String, 17 | members : Array[String], 18 | ) -> RedisInt { 19 | let args = ["SREM", key, ..members] 20 | self.send(args).to_int() 21 | } 22 | 23 | ///| 24 | pub async fn RedisClient::smembers( 25 | self : RedisClient, 26 | key : String, 27 | ) -> RedisArray { 28 | self.send(["SMEMBERS", key]).to_array() 29 | } 30 | 31 | ///| 32 | pub async fn RedisClient::scard(self : RedisClient, key : String) -> RedisInt { 33 | self.send(["SCARD", key]).to_int() 34 | } 35 | 36 | ///| 37 | pub async fn RedisClient::sismember( 38 | self : RedisClient, 39 | key : String, 40 | value : String, 41 | ) -> RedisInt { 42 | self.send(["SISMEMBER", key, value]).to_int() 43 | } 44 | 45 | ///| 46 | pub async fn RedisClient::smismember( 47 | self : RedisClient, 48 | key : String, 49 | members : Array[String], 50 | ) -> RedisArray { 51 | let args = ["SMISMEMBER", key, ..members] 52 | self.send(args).to_array() 53 | } 54 | 55 | ///| 56 | pub async fn RedisClient::spop(self : RedisClient, key : String) -> RedisString { 57 | self.send(["SPOP", key]).to_string() 58 | } 59 | 60 | ///| 61 | pub async fn RedisClient::srandmember( 62 | self : RedisClient, 63 | key : String, 64 | ) -> RedisString { 65 | self.send(["SRANDMEMBER", key]).to_string() 66 | } 67 | 68 | ///| 69 | pub async fn RedisClient::smove( 70 | self : RedisClient, 71 | source : String, 72 | destination : String, 73 | value : String, 74 | ) -> RedisInt { 75 | self.send(["SMOVE", source, destination, value]).to_int() 76 | } 77 | 78 | ///| 79 | pub async fn RedisClient::sdiff( 80 | self : RedisClient, 81 | keys : Array[String], 82 | ) -> RedisArray { 83 | let args = ["SDIFF", ..keys] 84 | self.send(args).to_array() 85 | } 86 | 87 | ///| 88 | pub async fn RedisClient::sdiffstore( 89 | self : RedisClient, 90 | destination : String, 91 | keys : Array[String], 92 | ) -> RedisInt { 93 | let args = ["SDIFFSTORE", destination, ..keys] 94 | self.send(args).to_int() 95 | } 96 | 97 | ///| 98 | pub async fn RedisClient::sinter( 99 | self : RedisClient, 100 | keys : Array[String], 101 | ) -> RedisInt { 102 | let args = ["SINTER", ..keys] 103 | self.send(args).to_int() 104 | } 105 | 106 | ///| 107 | pub async fn RedisClient::sintercard( 108 | self : RedisClient, 109 | numkeys : String, 110 | keys : Array[String], 111 | ) -> RedisInt { 112 | let args = ["SINTERCARD", numkeys, ..keys] 113 | self.send(args).to_int() 114 | } 115 | 116 | ///| 117 | pub async fn RedisClient::sinterstore( 118 | self : RedisClient, 119 | destination : String, 120 | keys : Array[String], 121 | ) -> RedisInt { 122 | let args = ["SINTERSTORE", destination, ..keys] 123 | self.send(args).to_int() 124 | } 125 | 126 | ///| 127 | pub async fn RedisClient::sunion( 128 | self : RedisClient, 129 | keys : Array[String], 130 | ) -> RedisInt { 131 | let args = ["SUNION", ..keys] 132 | self.send(args).to_int() 133 | } 134 | 135 | ///| 136 | pub async fn RedisClient::sunionstore( 137 | self : RedisClient, 138 | destination : String, 139 | keys : Array[String], 140 | ) -> RedisInt { 141 | let args = ["SUNIONSTORE", destination, ..keys] 142 | self.send(args).to_int() 143 | } 144 | 145 | ///| 146 | pub async fn RedisClient::sscan( 147 | self : RedisClient, 148 | key : String, 149 | cursor : Int, 150 | match_pattern? : String, 151 | count? : Int, 152 | ) -> RedisArray { 153 | let args = ["SSCAN", key, cursor.to_string()] 154 | if match_pattern is Some(m) { 155 | args.push("MATCH \{m}") 156 | } 157 | if count is Some(c) { 158 | args.push("COUNT \{c}") 159 | } 160 | self.send(args).to_array() 161 | } 162 | -------------------------------------------------------------------------------- /bitmap.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | pub(all) enum Bit { 3 | Zero 4 | One 5 | } 6 | 7 | ///| 8 | pub(all) enum BitFieldEncoding { 9 | U8 10 | U16 11 | U32 12 | U64 13 | I8 14 | I16 15 | I32 16 | I64 17 | } 18 | 19 | ///| 20 | pub(all) enum BitFieldOperation { 21 | Get(encoding~ : BitFieldEncoding, offset~ : Int) 22 | Set(encoding~ : BitFieldEncoding, offset~ : Int, value~ : Bit) 23 | Incrby(encoding~ : BitFieldEncoding, offset~ : Int, value~ : Int) 24 | Overflow(behavior~ : String) 25 | } 26 | 27 | ///| 28 | pub impl Show for BitFieldEncoding with output(self, logger : &Logger) -> Unit { 29 | logger.write_string(self.to_string()) 30 | } 31 | 32 | ///| 33 | pub impl Show for BitFieldEncoding with to_string(self) -> String { 34 | match self { 35 | U8 => "u8" 36 | U16 => "u16" 37 | U32 => "u32" 38 | U64 => "u64" 39 | I8 => "i8" 40 | I16 => "i16" 41 | I32 => "i32" 42 | I64 => "i64" 43 | } 44 | } 45 | 46 | ///| 47 | pub fn BitFieldOperation::to_array(self : BitFieldOperation) -> Array[String] { 48 | match self { 49 | Get(encoding~, offset~) => ["GET", encoding.to_string(), offset.to_string()] 50 | Set(encoding~, offset~, value~) => 51 | ["SET", encoding.to_string(), offset.to_string(), value.to_string()] 52 | Incrby(encoding~, offset~, value~) => 53 | ["INCRBY", encoding.to_string(), offset.to_string(), value.to_string()] 54 | Overflow(behavior~) => ["OVERFLOW", behavior] 55 | } 56 | } 57 | 58 | ///| 59 | pub impl Show for Bit with output(self : Bit, logger : &Logger) -> Unit { 60 | logger.write_string(self.to_string()) 61 | } 62 | 63 | ///| 64 | pub impl Show for Bit with to_string(self : Bit) -> String { 65 | match self { 66 | Zero => "0" 67 | One => "1" 68 | } 69 | } 70 | 71 | // ============ 位操作命令 ============ 72 | 73 | ///| 74 | pub async fn RedisClient::setbit( 75 | self : RedisClient, 76 | key : String, 77 | offset : Int, 78 | value : Bit, 79 | ) -> RedisInt { 80 | self.send(["SETBIT", key, offset.to_string(), value.to_string()]).to_int() 81 | } 82 | 83 | ///| 84 | pub async fn RedisClient::getbit( 85 | self : RedisClient, 86 | key : String, 87 | offset : Int, 88 | ) -> RedisInt { 89 | self.send(["GETBIT", key, offset.to_string()]).to_int() 90 | } 91 | 92 | ///| 93 | pub async fn RedisClient::bitcount( 94 | self : RedisClient, 95 | key : String, 96 | ) -> RedisInt { 97 | self.send(["BITCOUNT", key]).to_int() 98 | } 99 | 100 | ///| 101 | pub async fn RedisClient::bitcount_range( 102 | self : RedisClient, 103 | key : String, 104 | start : Int, 105 | end : Int, 106 | ) -> RedisInt { 107 | self.send(["BITCOUNT", key, start.to_string(), end.to_string()]).to_int() 108 | } 109 | 110 | ///| 111 | pub async fn RedisClient::bitfield( 112 | self : RedisClient, 113 | key : String, 114 | operations : Array[BitFieldOperation], 115 | ) -> RedisArray { 116 | let args = ["BITFIELD", key, ..operations.map(op => op.to_array()).flatten()] 117 | self.send(args).to_array() 118 | } 119 | 120 | ///| 121 | pub async fn RedisClient::bitfield_ro( 122 | self : RedisClient, 123 | key : String, 124 | operations : Array[String], 125 | ) -> RedisArray { 126 | let args = ["BITFIELD_RO", key, ..operations] 127 | self.send(args).to_array() 128 | } 129 | 130 | ///| 131 | pub async fn RedisClient::bitop( 132 | self : RedisClient, 133 | operation : String, 134 | destkey : String, 135 | keys : Array[String], 136 | ) -> RedisInt { 137 | let args = ["BITOP", operation, destkey, ..keys] 138 | self.send(args).to_int() 139 | } 140 | 141 | ///| 142 | pub async fn RedisClient::bitpos( 143 | self : RedisClient, 144 | key : String, 145 | bit : Bit, 146 | ) -> RedisInt { 147 | self.send(["BITPOS", key, bit.to_string()]).to_int() 148 | } 149 | 150 | ///| 151 | pub async fn RedisClient::bitpos_range( 152 | self : RedisClient, 153 | key : String, 154 | bit : Bit, 155 | start : Int, 156 | end : Int, 157 | ) -> RedisInt { 158 | self 159 | .send(["BITPOS", key, bit.to_string(), start.to_string(), end.to_string()]) 160 | .to_int() 161 | } 162 | -------------------------------------------------------------------------------- /connection.mbt: -------------------------------------------------------------------------------- 1 | // ============ 连接管理命令 ============ 2 | 3 | ///| 4 | pub async fn RedisClient::auth( 5 | self : RedisClient, 6 | password : String, 7 | ) -> RedisString { 8 | self.send(["AUTH", password]).to_string() 9 | } 10 | 11 | ///| 12 | pub async fn RedisClient::auth_username( 13 | self : RedisClient, 14 | username : String, 15 | password : String, 16 | ) -> RedisString { 17 | self.send(["AUTH", username, password]).to_string() 18 | } 19 | 20 | ///| 21 | pub async fn RedisClient::echo( 22 | self : RedisClient, 23 | message : String, 24 | ) -> RedisString { 25 | self.send(["ECHO", message]).to_string() 26 | } 27 | 28 | ///| 29 | pub async fn RedisClient::hello( 30 | self : RedisClient, 31 | protover : String, 32 | ) -> RedisArray { 33 | self.send(["HELLO", protover]).to_array() 34 | } 35 | 36 | ///| 37 | pub async fn RedisClient::quit(self : RedisClient) -> RedisString { 38 | self.send(["QUIT"]).to_string() 39 | } 40 | 41 | ///| 42 | pub async fn RedisClient::reset(self : RedisClient) -> RedisString { 43 | self.send(["RESET"]).to_string() 44 | } 45 | 46 | ///| 47 | pub async fn RedisClient::select( 48 | self : RedisClient, 49 | index : String, 50 | ) -> RedisString { 51 | self.send(["SELECT", index]).to_string() 52 | } 53 | 54 | ///| 55 | pub async fn RedisClient::client_caching( 56 | self : RedisClient, 57 | mode : String, 58 | ) -> RedisString { 59 | self.send(["CLIENT", "CACHING", mode]).to_string() 60 | } 61 | 62 | ///| 63 | pub async fn RedisClient::client_getname(self : RedisClient) -> RedisBulkString { 64 | self.send(["CLIENT", "GETNAME"]).to_bulk_string() 65 | } 66 | 67 | ///| 68 | pub async fn RedisClient::client_getredir(self : RedisClient) -> RedisInt { 69 | self.send(["CLIENT", "GETREDIR"]).to_int() 70 | } 71 | 72 | ///| 73 | pub async fn RedisClient::client_id(self : RedisClient) -> RedisInt { 74 | self.send(["CLIENT", "ID"]).to_int() 75 | } 76 | 77 | ///| 78 | pub async fn RedisClient::client_info(self : RedisClient) -> RedisString { 79 | self.send(["CLIENT", "INFO"]).to_string() 80 | } 81 | 82 | ///| 83 | pub async fn RedisClient::client_kill( 84 | self : RedisClient, 85 | ip_port : String, 86 | ) -> RedisInt { 87 | self.send(["CLIENT", "KILL", ip_port]).to_int() 88 | } 89 | 90 | ///| 91 | pub async fn RedisClient::client_list(self : RedisClient) -> RedisString { 92 | self.send(["CLIENT", "LIST"]).to_string() 93 | } 94 | 95 | ///| 96 | pub async fn RedisClient::client_no_evict( 97 | self : RedisClient, 98 | mode : String, 99 | ) -> RedisString { 100 | self.send(["CLIENT", "NO-EVICT", mode]).to_string() 101 | } 102 | 103 | ///| 104 | pub async fn RedisClient::client_no_touch( 105 | self : RedisClient, 106 | mode : String, 107 | ) -> RedisString { 108 | self.send(["CLIENT", "NO-TOUCH", mode]).to_string() 109 | } 110 | 111 | ///| 112 | pub async fn RedisClient::client_pause( 113 | self : RedisClient, 114 | timeout : String, 115 | ) -> RedisString { 116 | self.send(["CLIENT", "PAUSE", timeout]).to_string() 117 | } 118 | 119 | ///| 120 | pub async fn RedisClient::client_reply( 121 | self : RedisClient, 122 | mode : String, 123 | ) -> RedisString { 124 | self.send(["CLIENT", "REPLY", mode]).to_string() 125 | } 126 | 127 | ///| 128 | pub async fn RedisClient::client_setname( 129 | self : RedisClient, 130 | connection_name : String, 131 | ) -> RedisString { 132 | self.send(["CLIENT", "SETNAME", connection_name]).to_string() 133 | } 134 | 135 | ///| 136 | pub async fn RedisClient::client_tracking( 137 | self : RedisClient, 138 | mode : String, 139 | ) -> RedisString { 140 | self.send(["CLIENT", "TRACKING", mode]).to_string() 141 | } 142 | 143 | ///| 144 | pub async fn RedisClient::client_trackinginfo(self : RedisClient) -> RedisArray { 145 | self.send(["CLIENT", "TRACKINGINFO"]).to_array() 146 | } 147 | 148 | ///| 149 | pub async fn RedisClient::client_unblock( 150 | self : RedisClient, 151 | client_id : String, 152 | ) -> RedisInt { 153 | self.send(["CLIENT", "UNBLOCK", client_id]).to_int() 154 | } 155 | 156 | ///| 157 | pub async fn RedisClient::client_unpause(self : RedisClient) -> RedisString { 158 | self.send(["CLIENT", "UNPAUSE"]).to_string() 159 | } 160 | -------------------------------------------------------------------------------- /subscribe.mbt: -------------------------------------------------------------------------------- 1 | // ============ 发布订阅命令 ============ 2 | 3 | ///| 4 | /// 发布消息到指定频道 5 | /// 返回接收到消息的订阅者数量 6 | pub async fn RedisClient::publish( 7 | self : RedisClient, 8 | channel : String, 9 | message : String, 10 | ) -> RedisInt { 11 | self.send(["PUBLISH", channel, message]).to_int() 12 | } 13 | 14 | ///| 15 | /// 订阅一个或多个频道 16 | /// 返回订阅确认信息数组 17 | pub async fn RedisClient::subscribe( 18 | self : RedisClient, 19 | channels : Array[String], 20 | ) -> RedisArray { 21 | let args = ["SUBSCRIBE", ..channels] 22 | self.send(args).to_array() 23 | } 24 | 25 | ///| 26 | /// 取消订阅频道 27 | /// 如果不指定频道,则取消所有订阅 28 | pub async fn RedisClient::unsubscribe( 29 | self : RedisClient, 30 | channels? : Array[String], 31 | ) -> RedisArray { 32 | let args = match channels { 33 | Some(chs) => ["UNSUBSCRIBE", ..chs] 34 | None => ["UNSUBSCRIBE"] 35 | } 36 | self.send(args).to_array() 37 | } 38 | 39 | ///| 40 | /// 订阅一个或多个模式 41 | /// 返回模式订阅确认信息数组 42 | pub async fn RedisClient::psubscribe( 43 | self : RedisClient, 44 | patterns : Array[String], 45 | ) -> RedisArray { 46 | let args = ["PSUBSCRIBE", ..patterns] 47 | self.send(args).to_array() 48 | } 49 | 50 | ///| 51 | /// 取消订阅模式 52 | /// 如果不指定模式,则取消所有模式订阅 53 | pub async fn RedisClient::punsubscribe( 54 | self : RedisClient, 55 | patterns? : Array[String], 56 | ) -> RedisArray { 57 | let args = match patterns { 58 | Some(pts) => ["PUNSUBSCRIBE", ..pts] 59 | None => ["PUNSUBSCRIBE"] 60 | } 61 | self.send(args).to_array() 62 | } 63 | 64 | ///| 65 | /// 查看订阅与发布系统状态 66 | /// PUBSUB CHANNELS [pattern] - 列出当前活跃频道 67 | pub async fn RedisClient::pubsub_channels( 68 | self : RedisClient, 69 | pattern? : String, 70 | ) -> RedisArray { 71 | let args = match pattern { 72 | Some(p) => ["PUBSUB", "CHANNELS", p] 73 | None => ["PUBSUB", "CHANNELS"] 74 | } 75 | self.send(args).to_array() 76 | } 77 | 78 | ///| 79 | /// 查看订阅与发布系统状态 80 | /// PUBSUB NUMSUB [channel [channel ...]] - 返回频道订阅者数量 81 | pub async fn RedisClient::pubsub_numsub( 82 | self : RedisClient, 83 | channels? : Array[String], 84 | ) -> RedisArray { 85 | let args = match channels { 86 | Some(chs) => ["PUBSUB", "NUMSUB", ..chs] 87 | None => ["PUBSUB", "NUMSUB"] 88 | } 89 | self.send(args).to_array() 90 | } 91 | 92 | ///| 93 | /// 查看订阅与发布系统状态 94 | /// PUBSUB NUMPAT - 返回模式订阅数量 95 | pub async fn RedisClient::pubsub_numpat(self : RedisClient) -> RedisInt { 96 | self.send(["PUBSUB", "NUMPAT"]).to_int() 97 | } 98 | 99 | ///| 100 | /// 查看订阅与发布系统状态 101 | /// PUBSUB SHARDCHANNELS [pattern] - 列出分片频道 102 | pub async fn RedisClient::pubsub_shardchannels( 103 | self : RedisClient, 104 | pattern? : String, 105 | ) -> RedisArray { 106 | let args = match pattern { 107 | Some(p) => ["PUBSUB", "SHARDCHANNELS", p] 108 | None => ["PUBSUB", "SHARDCHANNELS"] 109 | } 110 | self.send(args).to_array() 111 | } 112 | 113 | ///| 114 | /// 查看订阅与发布系统状态 115 | /// PUBSUB SHARDNUMSUB [channel [channel ...]] - 返回分片频道订阅者数量 116 | pub async fn RedisClient::pubsub_shardnumsub( 117 | self : RedisClient, 118 | channels? : Array[String], 119 | ) -> RedisArray { 120 | let args = match channels { 121 | Some(chs) => ["PUBSUB", "SHARDNUMSUB", ..chs] 122 | None => ["PUBSUB", "SHARDNUMSUB"] 123 | } 124 | self.send(args).to_array() 125 | } 126 | 127 | ///| 128 | /// 发布消息到分片频道 129 | /// 返回接收到消息的订阅者数量 130 | pub async fn RedisClient::spublish( 131 | self : RedisClient, 132 | channel : String, 133 | message : String, 134 | ) -> RedisInt { 135 | self.send(["SPUBLISH", channel, message]).to_int() 136 | } 137 | 138 | ///| 139 | /// 订阅分片频道 140 | /// 返回订阅确认信息数组 141 | pub async fn RedisClient::ssubscribe( 142 | self : RedisClient, 143 | channels : Array[String], 144 | ) -> RedisArray { 145 | let args = ["SSUBSCRIBE", ..channels] 146 | self.send(args).to_array() 147 | } 148 | 149 | ///| 150 | /// 取消订阅分片频道 151 | /// 如果不指定频道,则取消所有分片频道订阅 152 | pub async fn RedisClient::sunsubscribe( 153 | self : RedisClient, 154 | channels? : Array[String], 155 | ) -> RedisArray { 156 | let args = match channels { 157 | Some(chs) => ["SUNSUBSCRIBE", ..chs] 158 | None => ["SUNSUBSCRIBE"] 159 | } 160 | self.send(args).to_array() 161 | } 162 | -------------------------------------------------------------------------------- /hash_test.mbt: -------------------------------------------------------------------------------- 1 | ///| Hash命令测试套件 2 | 3 | ///| 4 | /// 测试HSET和HGET命令 5 | async test "hash_hset_hget" { 6 | @async.with_task_group(fn(_root) { 7 | let client = connect("localhost", 6379) 8 | defer client.close() 9 | 10 | // 测试HSET 11 | let hset_result = client.hset("test:hash", "field1", "value1") 12 | assert_true(hset_result is Ok(1)) 13 | 14 | // 测试HGET 15 | let hget_result = client.hget("test:hash", "field1") 16 | assert_true(hget_result is Ok("value1")) 17 | 18 | // 清理 19 | let _ = client.del(["test:hash"]) 20 | 21 | }) 22 | } 23 | 24 | ///| 25 | /// 测试HDEL命令 26 | async test "hash_hdel" { 27 | @async.with_task_group(fn(_root) { 28 | let client = connect("localhost", 6379) 29 | defer client.close() 30 | 31 | // 设置测试数据 32 | let _ = client.hset("test:hash", "field1", "value1") 33 | let _ = client.hset("test:hash", "field2", "value2") 34 | 35 | // 测试HDEL 36 | let hdel_result = client.hdel("test:hash", ["field1"]) 37 | inspect(hdel_result is Ok(1), content="true") 38 | 39 | // 清理 40 | let _ = client.del(["test:hash"]) 41 | 42 | }) 43 | } 44 | 45 | ///| 46 | /// 测试HKEYS和HVALS命令 47 | async test "hash_hkeys_hvals" { 48 | @async.with_task_group(fn(_root) { 49 | let client = connect("localhost", 6379) 50 | defer client.close() 51 | 52 | // 设置测试数据 53 | let _ = client.hset("test:hash", "field1", "value1") 54 | let _ = client.hset("test:hash", "field2", "value2") 55 | 56 | // 测试HKEYS 57 | let hkeys_result = client.hkeys("test:hash") 58 | assert_true(hkeys_result is Ok(_)) 59 | 60 | // 测试HVALS 61 | let hvals_result = client.hvals("test:hash") 62 | assert_true(hvals_result is Ok(_)) 63 | 64 | // 清理 65 | let _ = client.del(["test:hash"]) 66 | 67 | }) 68 | } 69 | 70 | ///| 71 | /// 测试HGETALL命令 72 | async test "hash_hgetall" { 73 | @async.with_task_group(fn(_root) { 74 | let client = connect("localhost", 6379) 75 | defer client.close() 76 | 77 | // 设置测试数据 78 | let _ = client.hset("test:hash", "field1", "value1") 79 | let _ = client.hset("test:hash", "field2", "value2") 80 | 81 | // 测试HGETALL 82 | let hgetall_result = client.hgetall("test:hash") 83 | assert_true(hgetall_result is Ok(_)) 84 | 85 | // 清理 86 | let _ = client.del(["test:hash"]) 87 | 88 | }) 89 | } 90 | 91 | ///| 92 | /// 测试HEXISTS和HLEN命令 93 | async test "hash_hexists_hlen" { 94 | @async.with_task_group(fn(_root) { 95 | let client = connect("localhost", 6379) 96 | defer client.close() 97 | 98 | // 设置测试数据 99 | let _ = client.hset("test:hash", "field1", "value1") 100 | let _ = client.hset("test:hash", "field2", "value2") 101 | 102 | // 测试HEXISTS - 存在的字段 103 | let hexists_result1 = client.hexists("test:hash", "field1") 104 | assert_true(hexists_result1 is Ok(true)) 105 | 106 | // 测试HEXISTS - 不存在的字段 107 | let hexists_result2 = client.hexists("test:hash", "nonexistent") 108 | assert_true(hexists_result2 is Ok(false)) 109 | 110 | // 测试HLEN 111 | let hlen_result = client.hlen("test:hash") 112 | assert_true(hlen_result is Ok(2)) 113 | 114 | // 清理 115 | let _ = client.del(["test:hash"]) 116 | 117 | }) 118 | } 119 | 120 | ///| 121 | /// 测试HSET多个字段 122 | async test "hash_multiple_fields" { 123 | @async.with_task_group(fn(_root) { 124 | let client = connect("localhost", 6379) 125 | defer client.close() 126 | 127 | // 设置多个字段 128 | let _ = client.hset("test:hash", "field1", "value1") 129 | let _ = client.hset("test:hash", "field2", "value2") 130 | let _ = client.hset("test:hash", "field3", "value3") 131 | 132 | // 验证字段数量 133 | let hlen_result = client.hlen("test:hash") 134 | inspect(hlen_result is Ok(3), content="true") 135 | 136 | // 清理 137 | let _ = client.del(["test:hash"]) 138 | 139 | }) 140 | } 141 | 142 | ///| 143 | /// 测试hash字段操作 144 | async test "hash_field_operations" { 145 | @async.with_task_group(fn(_root) { 146 | let client = connect("localhost", 6379) 147 | defer client.close() 148 | 149 | // 设置字段 150 | let _ = client.hset("test:hash", "name", "Alice") 151 | let _ = client.hset("test:hash", "age", "25") 152 | 153 | // 检查字段是否存在 154 | let exists_result = client.hexists("test:hash", "name") 155 | assert_true(exists_result is Ok(true)) 156 | 157 | // 获取字段值 158 | let get_result = client.hget("test:hash", "name") 159 | assert_true(get_result is Ok("Alice")) 160 | 161 | // 清理 162 | let _ = client.del(["test:hash"]) 163 | 164 | }) 165 | } 166 | -------------------------------------------------------------------------------- /sorted_set_test.mbt: -------------------------------------------------------------------------------- 1 | ///| Sorted Set命令测试套件 2 | 3 | ///| 4 | /// 测试ZADD和ZREM命令 5 | async test "zset_zadd_zrem" { 6 | @async.with_task_group(fn(_root) { 7 | let client = connect("localhost", 6379) 8 | defer client.close() 9 | 10 | // 测试ZADD - 使用批量添加 11 | let zadd_result = client.zadd("test:zset", [ 12 | { score: "1", value: "member1" }, 13 | { score: "2", value: "member2" }, 14 | { score: "3", value: "member3" }, 15 | ]) 16 | assert_true(zadd_result is Ok(3)) // 添加了3个成员 17 | 18 | // 测试ZREM 19 | let zrem_result = client.zrem("test:zset", ["member1"]) 20 | assert_true(zrem_result is Ok(1)) 21 | 22 | // 清理 23 | let _ = client.del(["test:zset"]) 24 | 25 | }) 26 | } 27 | 28 | ///| 29 | /// 测试ZCARD命令 30 | async test "zset_zcard" { 31 | @async.with_task_group(fn(_root) { 32 | let client = connect("localhost", 6379) 33 | defer client.close() 34 | 35 | // 准备数据 36 | let _ = client.zadd("test:zset", [ 37 | { score: "1", value: "member1" }, 38 | { score: "2", value: "member2" }, 39 | { score: "3", value: "member3" }, 40 | ]) 41 | 42 | // 测试ZCARD 43 | let zcard_result = client.zcard("test:zset") 44 | assert_true(zcard_result is Ok(3)) 45 | 46 | // 清理 47 | let _ = client.del(["test:zset"]) 48 | 49 | }) 50 | } 51 | 52 | ///| 53 | /// 测试ZSCORE命令 54 | async test "zset_zscore" { 55 | @async.with_task_group(fn(_root) { 56 | let client = connect("localhost", 6379) 57 | defer client.close() 58 | 59 | // 准备数据 60 | let _ = client.zadd("test:zset", [ 61 | { score: "1.5", value: "member1" }, 62 | { score: "2.5", value: "member2" }, 63 | ]) 64 | 65 | // 测试ZSCORE 66 | let zscore_result = client.zscore("test:zset", "member1") 67 | assert_true(zscore_result is Ok("1.5")) 68 | 69 | // 清理 70 | let _ = client.del(["test:zset"]) 71 | 72 | }) 73 | } 74 | 75 | ///| 76 | /// 测试ZRANK命令 77 | async test "zset_zrank" { 78 | @async.with_task_group(fn(_root) { 79 | let client = connect("localhost", 6379) 80 | defer client.close() 81 | 82 | // 准备数据 83 | let _ = client.zadd("test:zset", [ 84 | { score: "1", value: "member1" }, 85 | { score: "2", value: "member2" }, 86 | { score: "3", value: "member3" }, 87 | ]) 88 | 89 | // 测试ZRANK 90 | let zrank_result = client.zrank("test:zset", "member2") 91 | assert_true(zrank_result is Ok(1)) // 0-based index 92 | 93 | // 清理 94 | let _ = client.del(["test:zset"]) 95 | 96 | }) 97 | } 98 | 99 | ///| 100 | /// 测试ZRANGE命令 101 | async test "zset_zrange" { 102 | @async.with_task_group(fn(_root) { 103 | let client = connect("localhost", 6379) 104 | defer client.close() 105 | 106 | // 准备数据 107 | let _ = client.zadd("test:zset", [ 108 | { score: "1", value: "member1" }, 109 | { score: "2", value: "member2" }, 110 | { score: "3", value: "member3" }, 111 | ]) 112 | 113 | // 测试ZRANGE 114 | let zrange_result = client.zrange("test:zset", start="0", stop="-1") 115 | assert_true(zrange_result is Ok(["member1", "member2", "member3"])) 116 | 117 | // 清理 118 | let _ = client.del(["test:zset"]) 119 | 120 | }) 121 | } 122 | 123 | ///| 124 | /// 测试ZREVRANGE命令 125 | async test "zset_zrevrange" { 126 | @async.with_task_group(fn(_root) { 127 | let client = connect("localhost", 6379) 128 | defer client.close() 129 | 130 | // 准备数据 131 | let _ = client.zadd("test:zset", [ 132 | { score: "1", value: "member1" }, 133 | { score: "2", value: "member2" }, 134 | { score: "3", value: "member3" }, 135 | ]) 136 | 137 | // 测试ZREVRANGE 138 | let zrevrange_result = client.zrevrange("test:zset", start="0", stop="-1") 139 | assert_true(zrevrange_result is Ok(["member3", "member2", "member1"])) 140 | 141 | // 清理 142 | let _ = client.del(["test:zset"]) 143 | 144 | }) 145 | } 146 | 147 | ///| 148 | /// 测试ZINCRBY命令 149 | async test "zset_zincrby" { 150 | @async.with_task_group(fn(_root) { 151 | let client = connect("localhost", 6379) 152 | defer client.close() 153 | 154 | // 清理 155 | let _ = client.del(["test:zset"]) 156 | // 准备数据 157 | let _ = client.zadd("test:zset", [{ score: "1", value: "member1" }]) 158 | 159 | // 测试ZINCRBY 160 | let zincrby_result = client.zincrby( 161 | "test:zset", 162 | increment="2.5", 163 | value="member1", 164 | ) 165 | assert_true(zincrby_result is Ok("3.5")) 166 | 167 | // 验证新分数 168 | let zscore_result = client.zscore("test:zset", "member1") 169 | assert_true(zscore_result is Ok("3.5")) 170 | 171 | // 清理 172 | let _ = client.del(["test:zset"]) 173 | 174 | }) 175 | } 176 | -------------------------------------------------------------------------------- /list.mbt: -------------------------------------------------------------------------------- 1 | // ============ 列表命令 ============ 2 | 3 | ///| 4 | pub async fn RedisClient::lpush( 5 | self : RedisClient, 6 | key : String, 7 | values : Array[String], 8 | ) -> RedisInt { 9 | let args = ["LPUSH", key, ..values] 10 | self.send(args).to_int() 11 | } 12 | 13 | ///| 14 | pub async fn RedisClient::rpush( 15 | self : RedisClient, 16 | key : String, 17 | values : Array[String], 18 | ) -> RedisInt { 19 | let args = ["RPUSH", key, ..values] 20 | self.send(args).to_int() 21 | } 22 | 23 | ///| 24 | pub async fn RedisClient::lpop(self : RedisClient, key : String) -> RedisString { 25 | self.send(["LPOP", key]).to_string() 26 | } 27 | 28 | ///| 29 | pub async fn RedisClient::rpop(self : RedisClient, key : String) -> RedisString { 30 | self.send(["RPOP", key]).to_string() 31 | } 32 | 33 | ///| 34 | pub async fn RedisClient::lpushx( 35 | self : RedisClient, 36 | key : String, 37 | values : Array[String], 38 | ) -> RedisInt { 39 | let args = ["LPUSHX", key, ..values] 40 | self.send(args).to_int() 41 | } 42 | 43 | ///| 44 | pub async fn RedisClient::rpushx( 45 | self : RedisClient, 46 | key : String, 47 | values : Array[String], 48 | ) -> RedisInt { 49 | let args = ["RPUSHX", key, ..values] 50 | self.send(args).to_int() 51 | } 52 | 53 | ///| 54 | pub async fn RedisClient::llen(self : RedisClient, key : String) -> RedisInt { 55 | self.send(["LLEN", key]).to_int() 56 | } 57 | 58 | ///| 59 | pub async fn RedisClient::lrange( 60 | self : RedisClient, 61 | key : String, 62 | start~ : Int, 63 | stop~ : Int, 64 | ) -> RedisArray { 65 | self.send(["LRANGE", key, start.to_string(), stop.to_string()]).to_array() 66 | } 67 | 68 | ///| 69 | pub async fn RedisClient::lindex( 70 | self : RedisClient, 71 | key : String, 72 | index~ : Int, 73 | ) -> RedisString { 74 | self.send(["LINDEX", key, index.to_string()]).to_string() 75 | } 76 | 77 | ///| 78 | pub async fn RedisClient::lset( 79 | self : RedisClient, 80 | key : String, 81 | index~ : Int, 82 | value~ : String, 83 | ) -> RedisString { 84 | self.send(["LSET", key, index.to_string(), value]).to_string() 85 | } 86 | 87 | ///| 88 | pub async fn RedisClient::lrem( 89 | self : RedisClient, 90 | key : String, 91 | count~ : Int, 92 | value~ : String, 93 | ) -> RedisInt { 94 | self.send(["LREM", key, count.to_string(), value]).to_int() 95 | } 96 | 97 | ///| 98 | pub async fn RedisClient::ltrim( 99 | self : RedisClient, 100 | key : String, 101 | start~ : Int, 102 | stop~ : Int, 103 | ) -> RedisString { 104 | self.send(["LTRIM", key, start.to_string(), stop.to_string()]).to_string() 105 | } 106 | 107 | ///| 108 | pub async fn RedisClient::linsert( 109 | self : RedisClient, 110 | key : String, 111 | where_~ : String, 112 | pivot~ : String, 113 | element~ : String, 114 | ) -> RedisInt { 115 | self.send(["LINSERT", key, where_, pivot, element]).to_int() 116 | } 117 | 118 | ///| 119 | pub async fn RedisClient::rpoplpush( 120 | self : RedisClient, 121 | source : String, 122 | destination : String, 123 | ) -> RedisValue { 124 | self.send(["RPOPLPUSH", source, destination]) 125 | } 126 | 127 | ///| 128 | pub async fn RedisClient::lmove( 129 | self : RedisClient, 130 | source : String, 131 | destination : String, 132 | wherefrom~ : Int, 133 | whereto~ : Int, 134 | ) -> RedisValue { 135 | self.send([ 136 | "LMOVE", 137 | source, 138 | destination, 139 | wherefrom.to_string(), 140 | whereto.to_string(), 141 | ]) 142 | } 143 | 144 | ///| 145 | pub async fn RedisClient::lpos( 146 | self : RedisClient, 147 | key : String, 148 | element~ : String, 149 | ) -> RedisValue { 150 | self.send(["LPOS", key, element]) 151 | } 152 | 153 | ///| 154 | pub async fn RedisClient::blpop( 155 | self : RedisClient, 156 | keys : Array[String], 157 | timeout~ : Int, 158 | ) -> RedisValue { 159 | let args = ["BLPOP", ..keys, timeout.to_string()] 160 | self.send(args) 161 | } 162 | 163 | ///| 164 | pub async fn RedisClient::brpop( 165 | self : RedisClient, 166 | keys : Array[String], 167 | timeout~ : Int, 168 | ) -> RedisValue { 169 | let args = ["BRPOP", ..keys, timeout.to_string()] 170 | self.send(args) 171 | } 172 | 173 | ///| 174 | pub async fn RedisClient::brpoplpush( 175 | self : RedisClient, 176 | source : String, 177 | destination : String, 178 | timeout~ : Int, 179 | ) -> RedisValue { 180 | self.send(["BRPOPLPUSH", source, destination, timeout.to_string()]) 181 | } 182 | 183 | ///| 184 | pub async fn RedisClient::blmove( 185 | self : RedisClient, 186 | source : String, 187 | destination : String, 188 | wherefrom~ : Int, 189 | whereto~ : Int, 190 | timeout~ : Int, 191 | ) -> RedisValue { 192 | self.send([ 193 | "BLMOVE", 194 | source, 195 | destination, 196 | wherefrom.to_string(), 197 | whereto.to_string(), 198 | timeout.to_string(), 199 | ]) 200 | } 201 | 202 | ///| 203 | pub async fn RedisClient::lmpop( 204 | self : RedisClient, 205 | numkeys : String, 206 | keys : Array[String], 207 | where_~ : String, 208 | ) -> RedisValue { 209 | let args = ["LMPOP", numkeys, ..keys, where_] 210 | self.send(args) 211 | } 212 | 213 | ///| 214 | pub async fn RedisClient::blmpop( 215 | self : RedisClient, 216 | timeout~ : Int, 217 | numkeys : String, 218 | keys : Array[String], 219 | where_~ : String, 220 | ) -> RedisValue { 221 | let args = ["BLMPOP", timeout.to_string(), numkeys, ..keys, where_] 222 | self.send(args) 223 | } 224 | -------------------------------------------------------------------------------- /json.mbt: -------------------------------------------------------------------------------- 1 | // ============ JSON 命令 ============ 2 | 3 | ///| 4 | /// JSON.SET - 设置JSON值 5 | pub async fn RedisClient::json_set( 6 | self : RedisClient, 7 | key : String, 8 | path : String, 9 | value : String, 10 | ) -> RedisJson { 11 | self.send(["JSON.SET", key, path, value]).to_json() 12 | } 13 | 14 | ///| 15 | /// JSON.GET - 获取JSON值 16 | pub async fn RedisClient::json_get( 17 | self : RedisClient, 18 | key : String, 19 | path : String, 20 | ) -> RedisJson { 21 | self.send(["JSON.GET", key, path]).to_json() 22 | } 23 | 24 | ///| 25 | /// JSON.DEL - 删除JSON路径 26 | pub async fn RedisClient::json_del( 27 | self : RedisClient, 28 | key : String, 29 | path : String, 30 | ) -> RedisInt { 31 | self.send(["JSON.DEL", key, path]).to_int() 32 | } 33 | 34 | ///| 35 | /// JSON.TYPE - 获取JSON值类型 36 | pub async fn RedisClient::json_type( 37 | self : RedisClient, 38 | key : String, 39 | path : String, 40 | ) -> RedisString { 41 | self.send(["JSON.TYPE", key, path]).to_string() 42 | } 43 | 44 | ///| 45 | /// JSON.STRLEN - 获取JSON字符串长度 46 | pub async fn RedisClient::json_strlen( 47 | self : RedisClient, 48 | key : String, 49 | path : String, 50 | ) -> RedisInt { 51 | self.send(["JSON.STRLEN", key, path]).to_int() 52 | } 53 | 54 | ///| 55 | /// JSON.ARRLEN - 获取JSON数组长度 56 | pub async fn RedisClient::json_arrlen( 57 | self : RedisClient, 58 | key : String, 59 | path : String, 60 | ) -> RedisInt { 61 | self.send(["JSON.ARRLEN", key, path]).to_int() 62 | } 63 | 64 | ///| 65 | /// JSON.ARRAPPEND - 向JSON数组追加元素 66 | pub async fn RedisClient::json_arrappend( 67 | self : RedisClient, 68 | key : String, 69 | path : String, 70 | values : Array[String], 71 | ) -> RedisInt { 72 | let args = ["JSON.ARRAPPEND", key, path, ..values] 73 | self.send(args).to_int() 74 | } 75 | 76 | ///| 77 | /// JSON.ARRINSERT - 在JSON数组指定位置插入元素 78 | pub async fn RedisClient::json_arrinsert( 79 | self : RedisClient, 80 | key : String, 81 | path : String, 82 | index : Int, 83 | values : Array[String], 84 | ) -> RedisInt { 85 | let args = ["JSON.ARRINSERT", key, path, index.to_string(), ..values] 86 | self.send(args).to_int() 87 | } 88 | 89 | ///| 90 | /// JSON.ARRPOP - 从JSON数组弹出元素 91 | pub async fn RedisClient::json_arrpop( 92 | self : RedisClient, 93 | key : String, 94 | path : String, 95 | index : Int, 96 | ) -> RedisString { 97 | self.send(["JSON.ARRPOP", key, path, index.to_string()]).to_string() 98 | } 99 | 100 | ///| 101 | /// JSON.ARRTRIM - 修剪JSON数组 102 | pub async fn RedisClient::json_arrtrim( 103 | self : RedisClient, 104 | key : String, 105 | path : String, 106 | start : Int, 107 | stop : Int, 108 | ) -> RedisInt { 109 | self 110 | .send(["JSON.ARRTRIM", key, path, start.to_string(), stop.to_string()]) 111 | .to_int() 112 | } 113 | 114 | ///| 115 | /// JSON.OBJKEYS - 获取JSON对象的键 116 | pub async fn RedisClient::json_objkeys( 117 | self : RedisClient, 118 | key : String, 119 | path : String, 120 | ) -> RedisArray { 121 | self.send(["JSON.OBJKEYS", key, path]).to_array() 122 | } 123 | 124 | ///| 125 | /// JSON.OBJLEN - 获取JSON对象的键数量 126 | pub async fn RedisClient::json_objlen( 127 | self : RedisClient, 128 | key : String, 129 | path : String, 130 | ) -> RedisInt { 131 | self.send(["JSON.OBJLEN", key, path]).to_int() 132 | } 133 | 134 | ///| 135 | /// JSON.NUMINCRBY - 对JSON数值进行增量操作 136 | pub async fn RedisClient::json_numincrby( 137 | self : RedisClient, 138 | key : String, 139 | path : String, 140 | value : String, 141 | ) -> RedisString { 142 | self.send(["JSON.NUMINCRBY", key, path, value]).to_string() 143 | } 144 | 145 | ///| 146 | /// JSON.NUMMULTBY - 对JSON数值进行乘法操作 147 | pub async fn RedisClient::json_nummultby( 148 | self : RedisClient, 149 | key : String, 150 | path : String, 151 | value : String, 152 | ) -> RedisString { 153 | self.send(["JSON.NUMMULTBY", key, path, value]).to_string() 154 | } 155 | 156 | ///| 157 | /// JSON.STRAPPEND - 向JSON字符串追加内容 158 | pub async fn RedisClient::json_strappend( 159 | self : RedisClient, 160 | key : String, 161 | path : String, 162 | value : String, 163 | ) -> RedisInt { 164 | self.send(["JSON.STRAPPEND", key, path, value]).to_int() 165 | } 166 | 167 | ///| 168 | /// JSON.TOGGLE - 切换JSON布尔值 169 | pub async fn RedisClient::json_toggle( 170 | self : RedisClient, 171 | key : String, 172 | path : String, 173 | ) -> RedisInt { 174 | self.send(["JSON.TOGGLE", key, path]).to_int() 175 | } 176 | 177 | ///| 178 | /// JSON.CLEAR - 清空JSON值 179 | pub async fn RedisClient::json_clear( 180 | self : RedisClient, 181 | key : String, 182 | path : String, 183 | ) -> RedisInt { 184 | self.send(["JSON.CLEAR", key, path]).to_int() 185 | } 186 | 187 | ///| 188 | /// JSON.FORGET - 删除JSON路径(JSON.DEL的别名) 189 | pub async fn RedisClient::json_forget( 190 | self : RedisClient, 191 | key : String, 192 | path : String, 193 | ) -> RedisInt { 194 | self.send(["JSON.FORGET", key, path]).to_int() 195 | } 196 | 197 | ///| 198 | /// JSON.RESP - 以RESP格式返回JSON值 199 | pub async fn RedisClient::json_resp( 200 | self : RedisClient, 201 | key : String, 202 | path : String, 203 | ) -> RedisArray { 204 | self.send(["JSON.RESP", key, path]).to_array() 205 | } 206 | 207 | ///| 208 | /// JSON.DEBUG - JSON调试命令 209 | pub async fn RedisClient::json_debug( 210 | self : RedisClient, 211 | subcommand : String, 212 | key : String, 213 | path : String, 214 | ) -> RedisString { 215 | self.send(["JSON.DEBUG", subcommand, key, path]).to_string() 216 | } 217 | -------------------------------------------------------------------------------- /string.mbt: -------------------------------------------------------------------------------- 1 | // ============ 字符串命令 ============ 2 | 3 | ///| 4 | pub async fn RedisClient::ping(self : RedisClient) -> RedisString { 5 | self.send(["PING"]).to_string() 6 | } 7 | 8 | ///| 9 | /// SET 命令的条件选项 10 | pub(all) enum SetCondition { 11 | /// 仅当键不存在时设置 12 | NX 13 | /// 仅当键存在时设置 14 | XX 15 | /// 仅当值等于比较值时设置 16 | IFEQ(String) 17 | } 18 | 19 | ///| 20 | /// SET 命令的过期时间选项 21 | pub(all) enum SetExpiration { 22 | /// 以秒为单位设置过期时间 23 | EX(Int) 24 | /// 以毫秒为单位设置过期时间 25 | PX(Int) 26 | /// 设置过期时间为 Unix 时间戳(秒) 27 | EXAT(Int) 28 | /// 设置过期时间为 Unix 时间戳(毫秒) 29 | PXAT(Int) 30 | /// 保持现有的 TTL 31 | KEEPTTL 32 | } 33 | 34 | ///| 35 | /// 基本的 SET 命令 36 | pub async fn RedisClient::set( 37 | self : RedisClient, 38 | key : String, 39 | value : String, 40 | condition? : SetCondition, 41 | get? : Bool = false, 42 | expiration? : SetExpiration, 43 | ) -> RedisString { 44 | let args = ["SET", key, value] 45 | 46 | // 添加条件选项 47 | if condition is Some(cond) { 48 | match cond { 49 | NX => args.push("NX") 50 | XX => args.push("XX") 51 | IFEQ(comparison) => { 52 | args.push("IFEQ") 53 | args.push(comparison) 54 | } 55 | } 56 | } 57 | 58 | // 添加 GET 选项 59 | if get == true { 60 | args.push("GET") 61 | } 62 | 63 | // 添加过期时间选项 64 | if expiration is Some(exp) { 65 | match exp { 66 | EX(seconds) => { 67 | args.push("EX") 68 | args.push(seconds.to_string()) 69 | } 70 | PX(milliseconds) => { 71 | args.push("PX") 72 | args.push(milliseconds.to_string()) 73 | } 74 | EXAT(timestamp) => { 75 | args.push("EXAT") 76 | args.push(timestamp.to_string()) 77 | } 78 | PXAT(timestamp) => { 79 | args.push("PXAT") 80 | args.push(timestamp.to_string()) 81 | } 82 | KEEPTTL => args.push("KEEPTTL") 83 | } 84 | } 85 | self.send(args).to_string() 86 | } 87 | 88 | ///| 89 | pub async fn RedisClient::get(self : RedisClient, key : String) -> RedisString { 90 | self.send(["GET", key]).to_string() 91 | } 92 | 93 | ///| 94 | pub async fn RedisClient::append( 95 | self : RedisClient, 96 | key : String, 97 | value : String, 98 | ) -> RedisInt { 99 | self.send(["APPEND", key, value]).to_int() 100 | } 101 | 102 | ///| 103 | pub async fn RedisClient::strlen(self : RedisClient, key : String) -> RedisInt { 104 | self.send(["STRLEN", key]).to_int() 105 | } 106 | 107 | ///| 108 | pub async fn RedisClient::incr(self : RedisClient, key : String) -> RedisInt { 109 | self.send(["INCR", key]).to_int() 110 | } 111 | 112 | ///| 113 | pub async fn RedisClient::decr(self : RedisClient, key : String) -> RedisInt { 114 | self.send(["DECR", key]).to_int() 115 | } 116 | 117 | ///| 118 | pub async fn RedisClient::incrby( 119 | self : RedisClient, 120 | key : String, 121 | increment : String, 122 | ) -> RedisInt { 123 | self.send(["INCRBY", key, increment]).to_int() 124 | } 125 | 126 | ///| 127 | pub async fn RedisClient::decrby( 128 | self : RedisClient, 129 | key : String, 130 | decrement : String, 131 | ) -> RedisInt { 132 | self.send(["DECRBY", key, decrement]).to_int() 133 | } 134 | 135 | ///| 136 | pub async fn RedisClient::incrbyfloat( 137 | self : RedisClient, 138 | key : String, 139 | increment : String, 140 | ) -> RedisString { 141 | self.send(["INCRBYFLOAT", key, increment]).to_string() 142 | } 143 | 144 | ///| 145 | pub async fn RedisClient::getrange( 146 | self : RedisClient, 147 | key : String, 148 | start : String, 149 | end : String, 150 | ) -> RedisString { 151 | self.send(["GETRANGE", key, start, end]).to_string() 152 | } 153 | 154 | ///| 155 | pub async fn RedisClient::setrange( 156 | self : RedisClient, 157 | key : String, 158 | offset : String, 159 | value : String, 160 | ) -> RedisInt { 161 | self.send(["SETRANGE", key, offset, value]).to_int() 162 | } 163 | 164 | ///| 165 | pub async fn RedisClient::getset( 166 | self : RedisClient, 167 | key : String, 168 | value : String, 169 | ) -> RedisString { 170 | self.send(["GETSET", key, value]).to_string() 171 | } 172 | 173 | ///| 174 | pub async fn RedisClient::getdel( 175 | self : RedisClient, 176 | key : String, 177 | ) -> RedisString { 178 | self.send(["GETDEL", key]).to_string() 179 | } 180 | 181 | ///| 182 | pub async fn RedisClient::getex( 183 | self : RedisClient, 184 | key : String, 185 | ex : String, 186 | ) -> RedisString { 187 | self.send(["GETEX", key, "EX", ex]).to_string() 188 | } 189 | 190 | ///| 191 | pub async fn RedisClient::setex( 192 | self : RedisClient, 193 | key : String, 194 | seconds : String, 195 | value : String, 196 | ) -> RedisString { 197 | self.send(["SETEX", key, seconds, value]).to_string() 198 | } 199 | 200 | ///| 201 | pub async fn RedisClient::psetex( 202 | self : RedisClient, 203 | key : String, 204 | milliseconds : String, 205 | value : String, 206 | ) -> RedisString { 207 | self.send(["PSETEX", key, milliseconds, value]).to_string() 208 | } 209 | 210 | ///| 211 | pub async fn RedisClient::setnx( 212 | self : RedisClient, 213 | key : String, 214 | value : String, 215 | ) -> RedisInt { 216 | self.send(["SETNX", key, value]).to_int() 217 | } 218 | 219 | ///| 220 | pub async fn RedisClient::mset( 221 | self : RedisClient, 222 | key_values : Map[String, String], 223 | ) -> RedisString { 224 | let arr = key_values 225 | .map(fn(k, v) -> Array[String] { [k, v] }) 226 | .values() 227 | .to_array() 228 | .flatten() 229 | let args = ["MSET", ..arr] 230 | self.send(args).to_string() 231 | } 232 | 233 | ///| 234 | pub async fn RedisClient::mget( 235 | self : RedisClient, 236 | keys : Array[String], 237 | ) -> RedisArray { 238 | let args = ["MGET", ..keys] 239 | self.send(args).to_array() 240 | } 241 | 242 | ///| 243 | pub async fn RedisClient::msetnx( 244 | self : RedisClient, 245 | key_values : Map[String, String], 246 | ) -> RedisInt { 247 | let arr = key_values 248 | .map(fn(k, v) -> Array[String] { [k, v] }) 249 | .values() 250 | .to_array() 251 | .flatten() 252 | let args = ["MSETNX", ..arr] 253 | self.send(args).to_int() 254 | } 255 | 256 | ///| 257 | pub async fn RedisClient::substr( 258 | self : RedisClient, 259 | key : String, 260 | start : Int, 261 | end : Int, 262 | ) -> RedisString { 263 | self.send(["SUBSTR", key, start.to_string(), end.to_string()]).to_string() 264 | } 265 | -------------------------------------------------------------------------------- /geo.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | pub(all) struct GeoCoordinates { 3 | longitude : Double 4 | latitude : Double 5 | } 6 | 7 | ///| 8 | pub fn GeoCoordinates::to_array(self : GeoCoordinates) -> Array[String] { 9 | [self.longitude.to_string(), self.latitude.to_string()] 10 | } 11 | 12 | ///| 13 | pub(all) enum GeoUnits { 14 | Meters 15 | Kilometers 16 | Miles 17 | Feet 18 | } 19 | 20 | ///| 21 | pub(all) struct GeoSearchByBox { 22 | width : Int 23 | height : Int 24 | unit : GeoUnits 25 | } 26 | 27 | ///| 28 | pub fn GeoSearchByBox::to_array(self : GeoSearchByBox) -> Array[String] { 29 | [self.width.to_string(), self.height.to_string(), self.unit.to_string()] 30 | } 31 | 32 | ///| 33 | pub impl Show for GeoUnits with output(self : GeoUnits, logger : &Logger) -> Unit { 34 | logger.write_string(self.to_string()) 35 | } 36 | 37 | ///| 38 | pub impl Show for GeoUnits with to_string(self : GeoUnits) -> String { 39 | match self { 40 | Meters => "m" 41 | Kilometers => "km" 42 | Miles => "mi" 43 | Feet => "ft" 44 | } 45 | } 46 | 47 | // ============ 地理位置命令 ============ 48 | 49 | ///| 50 | pub async fn RedisClient::geoadd( 51 | self : RedisClient, 52 | key : String, 53 | longitude : String, 54 | latitude : String, 55 | value : String, 56 | ) -> RedisInt { 57 | self.send(["GEOADD", key, longitude, latitude, value]).to_int() 58 | } 59 | 60 | ///| 61 | pub async fn RedisClient::geoadd_multiple( 62 | self : RedisClient, 63 | key : String, 64 | coordinates : Array[GeoCoordinates], 65 | ) -> RedisInt { 66 | let args = ["GEOADD", key, ..coordinates.map(c => c.to_array()).flatten()] 67 | self.send(args).to_int() 68 | } 69 | 70 | ///| 71 | pub async fn RedisClient::geodist( 72 | self : RedisClient, 73 | key : String, 74 | member1 : String, 75 | member2 : String, 76 | ) -> RedisBulkString { 77 | self.send(["GEODIST", key, member1, member2]).to_bulk_string() 78 | } 79 | 80 | ///| 81 | pub async fn RedisClient::geodist_unit( 82 | self : RedisClient, 83 | key : String, 84 | member1 : String, 85 | member2 : String, 86 | unit : GeoUnits, 87 | ) -> RedisBulkString { 88 | self 89 | .send(["GEODIST", key, member1, member2, unit.to_string()]) 90 | .to_bulk_string() 91 | } 92 | 93 | ///| 94 | pub async fn RedisClient::geohash( 95 | self : RedisClient, 96 | key : String, 97 | members : Array[String], 98 | ) -> RedisArray { 99 | let args = ["GEOHASH", key, ..members] 100 | self.send(args).to_array() 101 | } 102 | 103 | ///| 104 | pub async fn RedisClient::geopos( 105 | self : RedisClient, 106 | key : String, 107 | members : Array[String], 108 | ) -> RedisArray { 109 | let args = ["GEOPOS", key, ..members] 110 | self.send(args).to_array() 111 | } 112 | 113 | ///| 114 | pub async fn RedisClient::georadius( 115 | self : RedisClient, 116 | key : String, 117 | longitude : String, 118 | latitude : String, 119 | radius : String, 120 | unit : GeoUnits, 121 | ) -> RedisArray { 122 | self 123 | .send(["GEORADIUS", key, longitude, latitude, radius, unit.to_string()]) 124 | .to_array() 125 | } 126 | 127 | ///| 128 | pub async fn RedisClient::georadius_ro( 129 | self : RedisClient, 130 | key : String, 131 | longitude : String, 132 | latitude : String, 133 | radius : String, 134 | unit : GeoUnits, 135 | ) -> RedisArray { 136 | self 137 | .send(["GEORADIUS_RO", key, longitude, latitude, radius, unit.to_string()]) 138 | .to_array() 139 | } 140 | 141 | ///| 142 | pub async fn RedisClient::georadiusbymember( 143 | self : RedisClient, 144 | key : String, 145 | value : String, 146 | radius : String, 147 | unit : GeoUnits, 148 | ) -> RedisArray { 149 | self 150 | .send(["GEORADIUSBYMEMBER", key, value, radius, unit.to_string()]) 151 | .to_array() 152 | } 153 | 154 | ///| 155 | pub async fn RedisClient::georadiusbymember_ro( 156 | self : RedisClient, 157 | key : String, 158 | value : String, 159 | radius : String, 160 | unit : GeoUnits, 161 | ) -> RedisArray { 162 | self 163 | .send(["GEORADIUSBYMEMBER_RO", key, value, radius, unit.to_string()]) 164 | .to_array() 165 | } 166 | 167 | ///| 168 | pub async fn RedisClient::geosearch( 169 | self : RedisClient, 170 | key : String, 171 | member_ : String, 172 | radius : String, 173 | unit : GeoUnits, 174 | ) -> RedisArray { 175 | self 176 | .send([ 177 | "GEOSEARCH", 178 | key, 179 | "FROMMEMBER", 180 | member_, 181 | "BYRADIUS", 182 | radius, 183 | unit.to_string(), 184 | ]) 185 | .to_array() 186 | } 187 | 188 | ///| 189 | pub async fn RedisClient::geosearch_box( 190 | self : RedisClient, 191 | key : String, 192 | coordinates : GeoCoordinates, 193 | search_box : GeoSearchByBox, 194 | ) -> RedisArray { 195 | self 196 | .send( 197 | [ 198 | "GEOSEARCH", 199 | key, 200 | "FROMLONLAT", 201 | ..coordinates.to_array(), 202 | "BYBOX", 203 | ..search_box.to_array(), 204 | ], 205 | ) 206 | .to_array() 207 | } 208 | 209 | ///| 210 | pub async fn RedisClient::geosearchstore( 211 | self : RedisClient, 212 | source : String, 213 | destination : String, 214 | member_ : String, 215 | radius : String, 216 | unit : GeoUnits, 217 | ) -> RedisInt { 218 | self 219 | .send([ 220 | "GEOSEARCHSTORE", 221 | source, 222 | destination, 223 | "FROMMEMBER", 224 | member_, 225 | "BYRADIUS", 226 | radius, 227 | unit.to_string(), 228 | ]) 229 | .to_int() 230 | } 231 | 232 | ///| 233 | pub async fn RedisClient::geosearchstore_with_dist( 234 | self : RedisClient, 235 | destination : String, 236 | source : String, 237 | member_ : String, 238 | radius : Double, 239 | unit : GeoUnits, 240 | ) -> RedisInt { 241 | self 242 | .send([ 243 | "GEOSEARCHSTORE", 244 | destination, 245 | source, 246 | "FROMMEMBER", 247 | member_, 248 | "BYRADIUS", 249 | radius.to_string(), 250 | unit.to_string(), 251 | "STOREDIST", 252 | ]) 253 | .to_int() 254 | } 255 | 256 | ///| 257 | pub async fn RedisClient::geosearch_with_dist( 258 | self : RedisClient, 259 | key : String, 260 | member_ : String, 261 | radius : Double, 262 | unit : GeoUnits, 263 | ) -> RedisArray { 264 | self 265 | .send([ 266 | "GEOSEARCH", 267 | key, 268 | "FROMMEMBER", 269 | member_, 270 | "BYRADIUS", 271 | radius.to_string(), 272 | unit.to_string(), 273 | "WITHDIST", 274 | ]) 275 | .to_array() 276 | } 277 | 278 | ///| 279 | pub async fn RedisClient::geosearchstore_box( 280 | self : RedisClient, 281 | destination : String, 282 | source : String, 283 | from_member : String, 284 | search_box : GeoSearchByBox, 285 | ) -> RedisInt { 286 | self 287 | .send( 288 | [ 289 | "GEOSEARCHSTORE", 290 | destination, 291 | source, 292 | "FROMMEMBER", 293 | from_member, 294 | "BYBOX", 295 | ..search_box.to_array(), 296 | ], 297 | ) 298 | .to_int() 299 | } 300 | -------------------------------------------------------------------------------- /cluster.mbt: -------------------------------------------------------------------------------- 1 | // ============ 集群命令 ============ 2 | 3 | ///| 4 | pub async fn RedisClient::cluster_addslots( 5 | self : RedisClient, 6 | slots : Array[Int], 7 | ) -> RedisString { 8 | let slot_strings = slots.map(fn(slot) { slot.to_string() }) 9 | let args = ["CLUSTER", "ADDSLOTS", ..slot_strings] 10 | self.send(args).to_string() 11 | } 12 | 13 | ///| 14 | pub async fn RedisClient::cluster_addslotsrange( 15 | self : RedisClient, 16 | ranges : Array[(Int, Int)], 17 | ) -> RedisString { 18 | let range_strings = Array::new() 19 | for range in ranges { 20 | let (start, end) = range 21 | range_strings.push(start.to_string()) 22 | range_strings.push(end.to_string()) 23 | } 24 | let args = ["CLUSTER", "ADDSLOTSRANGE", ..range_strings] 25 | self.send(args).to_string() 26 | } 27 | 28 | ///| 29 | pub async fn RedisClient::cluster_bumpepoch(self : RedisClient) -> RedisString { 30 | self.send(["CLUSTER", "BUMPEPOCH"]).to_string() 31 | } 32 | 33 | ///| 34 | pub async fn RedisClient::cluster_count_failure_reports( 35 | self : RedisClient, 36 | node_id : String, 37 | ) -> RedisInt { 38 | self.send(["CLUSTER", "COUNT-FAILURE-REPORTS", node_id]).to_int() 39 | } 40 | 41 | ///| 42 | pub async fn RedisClient::cluster_countkeysinslot( 43 | self : RedisClient, 44 | slot : Int, 45 | ) -> RedisInt { 46 | self.send(["CLUSTER", "COUNTKEYSINSLOT", slot.to_string()]).to_int() 47 | } 48 | 49 | ///| 50 | pub async fn RedisClient::cluster_delslots( 51 | self : RedisClient, 52 | slots : Array[Int], 53 | ) -> RedisString { 54 | let slot_strings = slots.map(fn(slot) { slot.to_string() }) 55 | let args = ["CLUSTER", "DELSLOTS", ..slot_strings] 56 | self.send(args).to_string() 57 | } 58 | 59 | ///| 60 | pub async fn RedisClient::cluster_delslotsrange( 61 | self : RedisClient, 62 | ranges : Array[(Int, Int)], 63 | ) -> RedisString { 64 | let range_strings = Array::new() 65 | for range in ranges { 66 | let (start, end) = range 67 | range_strings.push(start.to_string()) 68 | range_strings.push(end.to_string()) 69 | } 70 | let args = ["CLUSTER", "DELSLOTSRANGE", ..range_strings] 71 | self.send(args).to_string() 72 | } 73 | 74 | ///| 75 | pub async fn RedisClient::cluster_failover( 76 | self : RedisClient, 77 | option? : String, 78 | ) -> RedisString { 79 | let args = match option { 80 | Some(opt) => ["CLUSTER", "FAILOVER", opt] 81 | None => ["CLUSTER", "FAILOVER"] 82 | } 83 | self.send(args).to_string() 84 | } 85 | 86 | ///| 87 | pub async fn RedisClient::cluster_flushslots(self : RedisClient) -> RedisString { 88 | self.send(["CLUSTER", "FLUSHSLOTS"]).to_string() 89 | } 90 | 91 | ///| 92 | pub async fn RedisClient::cluster_forget( 93 | self : RedisClient, 94 | node_id : String, 95 | ) -> RedisString { 96 | self.send(["CLUSTER", "FORGET", node_id]).to_string() 97 | } 98 | 99 | ///| 100 | pub async fn RedisClient::cluster_getkeysinslot( 101 | self : RedisClient, 102 | slot : Int, 103 | count : Int, 104 | ) -> RedisArray { 105 | self 106 | .send(["CLUSTER", "GETKEYSINSLOT", slot.to_string(), count.to_string()]) 107 | .to_array() 108 | } 109 | 110 | ///| 111 | pub async fn RedisClient::cluster_info(self : RedisClient) -> RedisString { 112 | self.send(["CLUSTER", "INFO"]).to_string() 113 | } 114 | 115 | ///| 116 | pub async fn RedisClient::cluster_keyslot( 117 | self : RedisClient, 118 | key : String, 119 | ) -> RedisInt { 120 | self.send(["CLUSTER", "KEYSLOT", key]).to_int() 121 | } 122 | 123 | ///| 124 | pub async fn RedisClient::cluster_links(self : RedisClient) -> RedisArray { 125 | self.send(["CLUSTER", "LINKS"]).to_array() 126 | } 127 | 128 | ///| 129 | pub async fn RedisClient::cluster_meet( 130 | self : RedisClient, 131 | ip : String, 132 | port : Int, 133 | cluster_bus_port? : Int, 134 | ) -> RedisString { 135 | let args = match cluster_bus_port { 136 | Some(bus_port) => 137 | ["CLUSTER", "MEET", ip, port.to_string(), bus_port.to_string()] 138 | None => ["CLUSTER", "MEET", ip, port.to_string()] 139 | } 140 | self.send(args).to_string() 141 | } 142 | 143 | ///| 144 | pub async fn RedisClient::cluster_myid(self : RedisClient) -> RedisString { 145 | self.send(["CLUSTER", "MYID"]).to_string() 146 | } 147 | 148 | ///| 149 | pub async fn RedisClient::cluster_myshardid(self : RedisClient) -> RedisString { 150 | self.send(["CLUSTER", "MYSHARDID"]).to_string() 151 | } 152 | 153 | ///| 154 | pub async fn RedisClient::cluster_nodes(self : RedisClient) -> RedisString { 155 | self.send(["CLUSTER", "NODES"]).to_string() 156 | } 157 | 158 | ///| 159 | pub async fn RedisClient::cluster_replicas( 160 | self : RedisClient, 161 | node_id : String, 162 | ) -> RedisArray { 163 | self.send(["CLUSTER", "REPLICAS", node_id]).to_array() 164 | } 165 | 166 | ///| 167 | pub async fn RedisClient::cluster_replicate( 168 | self : RedisClient, 169 | node_id : String, 170 | ) -> RedisString { 171 | self.send(["CLUSTER", "REPLICATE", node_id]).to_string() 172 | } 173 | 174 | ///| 175 | pub async fn RedisClient::cluster_reset( 176 | self : RedisClient, 177 | reset_type? : String, 178 | ) -> RedisString { 179 | let args = match reset_type { 180 | Some(type_) => ["CLUSTER", "RESET", type_] 181 | None => ["CLUSTER", "RESET"] 182 | } 183 | self.send(args).to_string() 184 | } 185 | 186 | ///| 187 | pub async fn RedisClient::cluster_saveconfig(self : RedisClient) -> RedisString { 188 | self.send(["CLUSTER", "SAVECONFIG"]).to_string() 189 | } 190 | 191 | ///| 192 | pub async fn RedisClient::cluster_set_config_epoch( 193 | self : RedisClient, 194 | config_epoch : Int, 195 | ) -> RedisString { 196 | self 197 | .send(["CLUSTER", "SET-CONFIG-EPOCH", config_epoch.to_string()]) 198 | .to_string() 199 | } 200 | 201 | ///| 202 | pub async fn RedisClient::cluster_setslot( 203 | self : RedisClient, 204 | slot : Int, 205 | subcommand : String, 206 | node_id? : String, 207 | ) -> RedisString { 208 | let args = match node_id { 209 | Some(id) => ["CLUSTER", "SETSLOT", slot.to_string(), subcommand, id] 210 | None => ["CLUSTER", "SETSLOT", slot.to_string(), subcommand] 211 | } 212 | self.send(args).to_string() 213 | } 214 | 215 | ///| 216 | pub async fn RedisClient::cluster_shards(self : RedisClient) -> RedisArray { 217 | self.send(["CLUSTER", "SHARDS"]).to_array() 218 | } 219 | 220 | ///| 221 | pub async fn RedisClient::cluster_slaves( 222 | self : RedisClient, 223 | node_id : String, 224 | ) -> RedisArray { 225 | self.send(["CLUSTER", "SLAVES", node_id]).to_array() 226 | } 227 | 228 | ///| 229 | pub async fn RedisClient::cluster_slot_stats( 230 | self : RedisClient, 231 | slots : Array[Int], 232 | ) -> RedisArray { 233 | let slot_strings = slots.map(fn(slot) { slot.to_string() }) 234 | let args = ["CLUSTER", "SLOTS", "STATS", ..slot_strings] 235 | self.send(args).to_array() 236 | } 237 | 238 | ///| 239 | pub async fn RedisClient::cluster_slots(self : RedisClient) -> RedisArray { 240 | self.send(["CLUSTER", "SLOTS"]).to_array() 241 | } 242 | -------------------------------------------------------------------------------- /generic.mbt: -------------------------------------------------------------------------------- 1 | // ============ 通用命令 ============ 2 | 3 | ///| 4 | pub async fn RedisClient::del( 5 | self : RedisClient, 6 | keys : Array[String], 7 | ) -> RedisInt { 8 | let args = ["DEL", ..keys] 9 | self.send(args).to_int() 10 | } 11 | 12 | ///| 13 | pub async fn RedisClient::unlink( 14 | self : RedisClient, 15 | keys : Array[String], 16 | ) -> RedisInt { 17 | let args = ["UNLINK", ..keys] 18 | self.send(args).to_int() 19 | } 20 | 21 | ///| 22 | pub async fn RedisClient::exists( 23 | self : RedisClient, 24 | keys : Array[String], 25 | ) -> RedisInt { 26 | let args = ["EXISTS", ..keys] 27 | self.send(args).to_int() 28 | } 29 | 30 | ///| 31 | pub async fn RedisClient::expire( 32 | self : RedisClient, 33 | key : String, 34 | seconds : String, 35 | ) -> RedisInt { 36 | self.send(["EXPIRE", key, seconds]).to_int() 37 | } 38 | 39 | ///| 40 | pub async fn RedisClient::expireat( 41 | self : RedisClient, 42 | key : String, 43 | timestamp : String, 44 | ) -> RedisInt { 45 | self.send(["EXPIREAT", key, timestamp]).to_int() 46 | } 47 | 48 | ///| 49 | pub async fn RedisClient::expiretime( 50 | self : RedisClient, 51 | key : String, 52 | ) -> RedisInt { 53 | self.send(["EXPIRETIME", key]).to_int() 54 | } 55 | 56 | ///| 57 | pub async fn RedisClient::pexpire( 58 | self : RedisClient, 59 | key : String, 60 | milliseconds : String, 61 | ) -> RedisInt { 62 | self.send(["PEXPIRE", key, milliseconds]).to_int() 63 | } 64 | 65 | ///| 66 | pub async fn RedisClient::pexpireat( 67 | self : RedisClient, 68 | key : String, 69 | timestamp : String, 70 | ) -> RedisInt { 71 | self.send(["PEXPIREAT", key, timestamp]).to_int() 72 | } 73 | 74 | ///| 75 | pub async fn RedisClient::pexpiretime( 76 | self : RedisClient, 77 | key : String, 78 | ) -> RedisInt { 79 | self.send(["PEXPIRETIME", key]).to_int() 80 | } 81 | 82 | ///| 83 | pub async fn RedisClient::ttl(self : RedisClient, key : String) -> RedisInt { 84 | self.send(["TTL", key]).to_int() 85 | } 86 | 87 | ///| 88 | pub async fn RedisClient::pttl(self : RedisClient, key : String) -> RedisInt { 89 | self.send(["PTTL", key]).to_int() 90 | } 91 | 92 | ///| 93 | pub async fn RedisClient::persist(self : RedisClient, key : String) -> RedisInt { 94 | self.send(["PERSIST", key]).to_int() 95 | } 96 | 97 | ///| 98 | pub async fn RedisClient::keys( 99 | self : RedisClient, 100 | pattern : String, 101 | ) -> RedisArray { 102 | self.send(["KEYS", pattern]).to_array() 103 | } 104 | 105 | ///| 106 | pub async fn RedisClient::scan( 107 | self : RedisClient, 108 | cursor : String, 109 | ) -> RedisArray { 110 | self.send(["SCAN", cursor]).to_array() 111 | } 112 | 113 | ///| 114 | pub async fn RedisClient::randomkey(self : RedisClient) -> RedisBulkString { 115 | self.send(["RANDOMKEY"]).to_bulk_string() 116 | } 117 | 118 | ///| 119 | pub async fn RedisClient::rename( 120 | self : RedisClient, 121 | key : String, 122 | newkey : String, 123 | ) -> RedisString { 124 | self.send(["RENAME", key, newkey]).to_string() 125 | } 126 | 127 | ///| 128 | pub async fn RedisClient::renamenx( 129 | self : RedisClient, 130 | key : String, 131 | newkey : String, 132 | ) -> RedisInt { 133 | self.send(["RENAMENX", key, newkey]).to_int() 134 | } 135 | 136 | ///| 137 | pub async fn RedisClient::type_( 138 | self : RedisClient, 139 | key : String, 140 | ) -> RedisString { 141 | self.send(["TYPE", key]).to_string() 142 | } 143 | 144 | ///| 145 | pub async fn RedisClient::sort(self : RedisClient, key : String) -> RedisArray { 146 | self.send(["SORT", key]).to_array() 147 | } 148 | 149 | ///| 150 | pub async fn RedisClient::sort_ro( 151 | self : RedisClient, 152 | key : String, 153 | ) -> RedisArray { 154 | self.send(["SORT_RO", key]).to_array() 155 | } 156 | 157 | ///| 158 | pub async fn RedisClient::copy( 159 | self : RedisClient, 160 | source : String, 161 | destination : String, 162 | ) -> RedisInt { 163 | self.send(["COPY", source, destination]).to_int() 164 | } 165 | 166 | ///| 167 | pub async fn RedisClient::move_( 168 | self : RedisClient, 169 | key : String, 170 | db : String, 171 | ) -> RedisInt { 172 | self.send(["MOVE", key, db]).to_int() 173 | } 174 | 175 | ///| 176 | pub async fn RedisClient::swapdb( 177 | self : RedisClient, 178 | index1 : String, 179 | index2 : String, 180 | ) -> RedisString { 181 | self.send(["SWAPDB", index1, index2]).to_string() 182 | } 183 | 184 | ///| 185 | pub async fn RedisClient::touch( 186 | self : RedisClient, 187 | keys : Array[String], 188 | ) -> RedisInt { 189 | let args = ["TOUCH", ..keys] 190 | self.send(args).to_int() 191 | } 192 | 193 | ///| 194 | pub async fn RedisClient::dump( 195 | self : RedisClient, 196 | key : String, 197 | ) -> RedisBulkString { 198 | self.send(["DUMP", key]).to_bulk_string() 199 | } 200 | 201 | ///| 202 | pub async fn RedisClient::restore( 203 | self : RedisClient, 204 | key : String, 205 | ttl : String, 206 | serialized_value : String, 207 | ) -> RedisString { 208 | self.send(["RESTORE", key, ttl, serialized_value]).to_string() 209 | } 210 | 211 | ///| 212 | pub async fn RedisClient::migrate( 213 | self : RedisClient, 214 | host : String, 215 | port : String, 216 | key : String, 217 | destination_db : String, 218 | timeout : String, 219 | ) -> RedisString { 220 | self.send(["MIGRATE", host, port, key, destination_db, timeout]).to_string() 221 | } 222 | 223 | ///| 224 | pub async fn RedisClient::object_encoding( 225 | self : RedisClient, 226 | key : String, 227 | ) -> RedisBulkString { 228 | self.send(["OBJECT", "ENCODING", key]).to_bulk_string() 229 | } 230 | 231 | ///| 232 | pub async fn RedisClient::object_freq( 233 | self : RedisClient, 234 | key : String, 235 | ) -> RedisInt { 236 | self.send(["OBJECT", "FREQ", key]).to_int() 237 | } 238 | 239 | ///| 240 | pub async fn RedisClient::object_idletime( 241 | self : RedisClient, 242 | key : String, 243 | ) -> RedisInt { 244 | self.send(["OBJECT", "IDLETIME", key]).to_int() 245 | } 246 | 247 | ///| 248 | pub async fn RedisClient::object_refcount( 249 | self : RedisClient, 250 | key : String, 251 | ) -> RedisInt { 252 | self.send(["OBJECT", "REFCOUNT", key]).to_int() 253 | } 254 | 255 | ///| 256 | pub async fn RedisClient::flushdb(self : RedisClient) -> RedisString { 257 | self.send(["FLUSHDB"]).to_string() 258 | } 259 | 260 | ///| 261 | pub async fn RedisClient::flushall(self : RedisClient) -> RedisString { 262 | self.send(["FLUSHALL"]).to_string() 263 | } 264 | 265 | ///| 266 | pub async fn RedisClient::dbsize(self : RedisClient) -> RedisInt { 267 | self.send(["DBSIZE"]).to_int() 268 | } 269 | 270 | ///| 271 | pub async fn RedisClient::wait( 272 | self : RedisClient, 273 | numreplicas : String, 274 | timeout : String, 275 | ) -> RedisInt { 276 | self.send(["WAIT", numreplicas, timeout]).to_int() 277 | } 278 | 279 | ///| 280 | pub async fn RedisClient::waitaof( 281 | self : RedisClient, 282 | numlocal : String, 283 | numreplicas : String, 284 | timeout : String, 285 | ) -> RedisInt { 286 | self.send(["WAITAOF", numlocal, numreplicas, timeout]).to_int() 287 | } 288 | -------------------------------------------------------------------------------- /server.mbt: -------------------------------------------------------------------------------- 1 | // ============ 服务器管理命令 ============ 2 | 3 | ///| 4 | pub async fn RedisClient::bgrewriteaof(self : RedisClient) -> RedisValue { 5 | self.send(["BGREWRITEAOF"]) 6 | } 7 | 8 | ///| 9 | pub async fn RedisClient::bgsave(self : RedisClient) -> RedisValue { 10 | self.send(["BGSAVE"]) 11 | } 12 | 13 | ///| 14 | pub async fn RedisClient::command(self : RedisClient) -> RedisValue { 15 | self.send(["COMMAND"]) 16 | } 17 | 18 | ///| 19 | pub async fn RedisClient::command_count(self : RedisClient) -> RedisValue { 20 | self.send(["COMMAND", "COUNT"]) 21 | } 22 | 23 | ///| 24 | pub async fn RedisClient::command_docs( 25 | self : RedisClient, 26 | command_names : Array[String], 27 | ) -> RedisValue { 28 | let args = ["COMMAND", "DOCS", ..command_names] 29 | self.send(args) 30 | } 31 | 32 | ///| 33 | pub async fn RedisClient::command_getkeys( 34 | self : RedisClient, 35 | command : Array[String], 36 | ) -> RedisValue { 37 | let args = ["COMMAND", "GETKEYS", ..command] 38 | self.send(args) 39 | } 40 | 41 | ///| 42 | pub async fn RedisClient::command_getkeysandflags( 43 | self : RedisClient, 44 | command : Array[String], 45 | ) -> RedisValue { 46 | let args = ["COMMAND", "GETKEYSANDFLAGS", ..command] 47 | self.send(args) 48 | } 49 | 50 | ///| 51 | pub async fn RedisClient::command_info( 52 | self : RedisClient, 53 | command_names : Array[String], 54 | ) -> RedisValue { 55 | let args = ["COMMAND", "INFO", ..command_names] 56 | self.send(args) 57 | } 58 | 59 | ///| 60 | pub async fn RedisClient::command_list(self : RedisClient) -> RedisValue { 61 | self.send(["COMMAND", "LIST"]) 62 | } 63 | 64 | ///| 65 | pub async fn RedisClient::config_get( 66 | self : RedisClient, 67 | parameter : String, 68 | ) -> RedisValue { 69 | self.send(["CONFIG", "GET", parameter]) 70 | } 71 | 72 | ///| 73 | pub async fn RedisClient::config_resetstat(self : RedisClient) -> RedisValue { 74 | self.send(["CONFIG", "RESETSTAT"]) 75 | } 76 | 77 | ///| 78 | pub async fn RedisClient::config_rewrite(self : RedisClient) -> RedisValue { 79 | self.send(["CONFIG", "REWRITE"]) 80 | } 81 | 82 | ///| 83 | pub async fn RedisClient::config_set( 84 | self : RedisClient, 85 | parameter : String, 86 | value : String, 87 | ) -> RedisValue { 88 | self.send(["CONFIG", "SET", parameter, value]) 89 | } 90 | 91 | ///| 92 | pub async fn RedisClient::debug_object( 93 | self : RedisClient, 94 | key : String, 95 | ) -> RedisValue { 96 | self.send(["DEBUG", "OBJECT", key]) 97 | } 98 | 99 | ///| 100 | pub async fn RedisClient::debug_segfault(self : RedisClient) -> RedisValue { 101 | self.send(["DEBUG", "SEGFAULT"]) 102 | } 103 | 104 | ///| 105 | pub async fn RedisClient::info(self : RedisClient) -> RedisValue { 106 | self.send(["INFO"]) 107 | } 108 | 109 | ///| 110 | pub async fn RedisClient::info_section( 111 | self : RedisClient, 112 | section : String, 113 | ) -> RedisValue { 114 | self.send(["INFO", section]) 115 | } 116 | 117 | ///| 118 | pub async fn RedisClient::lastsave(self : RedisClient) -> RedisValue { 119 | self.send(["LASTSAVE"]) 120 | } 121 | 122 | ///| 123 | pub async fn RedisClient::latency_doctor(self : RedisClient) -> RedisValue { 124 | self.send(["LATENCY", "DOCTOR"]) 125 | } 126 | 127 | ///| 128 | pub async fn RedisClient::latency_graph( 129 | self : RedisClient, 130 | event : String, 131 | ) -> RedisValue { 132 | self.send(["LATENCY", "GRAPH", event]) 133 | } 134 | 135 | ///| 136 | pub async fn RedisClient::latency_history( 137 | self : RedisClient, 138 | event : String, 139 | ) -> RedisValue { 140 | self.send(["LATENCY", "HISTORY", event]) 141 | } 142 | 143 | ///| 144 | pub async fn RedisClient::latency_latest(self : RedisClient) -> RedisValue { 145 | self.send(["LATENCY", "LATEST"]) 146 | } 147 | 148 | ///| 149 | pub async fn RedisClient::latency_reset( 150 | self : RedisClient, 151 | events : Array[String], 152 | ) -> RedisValue { 153 | let args = ["LATENCY", "RESET", ..events] 154 | self.send(args) 155 | } 156 | 157 | ///| 158 | pub async fn RedisClient::memory_doctor(self : RedisClient) -> RedisValue { 159 | self.send(["MEMORY", "DOCTOR"]) 160 | } 161 | 162 | ///| 163 | pub async fn RedisClient::memory_malloc_stats(self : RedisClient) -> RedisValue { 164 | self.send(["MEMORY", "MALLOC-STATS"]) 165 | } 166 | 167 | ///| 168 | pub async fn RedisClient::memory_purge(self : RedisClient) -> RedisValue { 169 | self.send(["MEMORY", "PURGE"]) 170 | } 171 | 172 | ///| 173 | pub async fn RedisClient::memory_stats(self : RedisClient) -> RedisValue { 174 | self.send(["MEMORY", "STATS"]) 175 | } 176 | 177 | ///| 178 | pub async fn RedisClient::memory_usage( 179 | self : RedisClient, 180 | key : String, 181 | ) -> RedisValue { 182 | self.send(["MEMORY", "USAGE", key]) 183 | } 184 | 185 | ///| 186 | pub async fn RedisClient::module_list(self : RedisClient) -> RedisValue { 187 | self.send(["MODULE", "LIST"]) 188 | } 189 | 190 | ///| 191 | pub async fn RedisClient::module_load( 192 | self : RedisClient, 193 | path : String, 194 | ) -> RedisValue { 195 | self.send(["MODULE", "LOAD", path]) 196 | } 197 | 198 | ///| 199 | pub async fn RedisClient::module_loadex( 200 | self : RedisClient, 201 | path : String, 202 | args : Array[String], 203 | ) -> RedisValue { 204 | let command_args = ["MODULE", "LOADEX", path, ..args] 205 | self.send(command_args) 206 | } 207 | 208 | ///| 209 | pub async fn RedisClient::module_unload( 210 | self : RedisClient, 211 | name : String, 212 | ) -> RedisValue { 213 | self.send(["MODULE", "UNLOAD", name]) 214 | } 215 | 216 | ///| 217 | pub async fn RedisClient::monitor(self : RedisClient) -> RedisValue { 218 | self.send(["MONITOR"]) 219 | } 220 | 221 | ///| 222 | pub async fn RedisClient::psync( 223 | self : RedisClient, 224 | replicationid : String, 225 | offset : String, 226 | ) -> RedisValue { 227 | self.send(["PSYNC", replicationid, offset]) 228 | } 229 | 230 | ///| 231 | pub async fn RedisClient::replconf( 232 | self : RedisClient, 233 | option : String, 234 | value : String, 235 | ) -> RedisValue { 236 | self.send(["REPLCONF", option, value]) 237 | } 238 | 239 | ///| 240 | pub async fn RedisClient::replicaof( 241 | self : RedisClient, 242 | host : String, 243 | port : String, 244 | ) -> RedisValue { 245 | self.send(["REPLICAOF", host, port]) 246 | } 247 | 248 | ///| 249 | pub async fn RedisClient::role(self : RedisClient) -> RedisValue { 250 | self.send(["ROLE"]) 251 | } 252 | 253 | ///| 254 | pub async fn RedisClient::save(self : RedisClient) -> RedisValue { 255 | self.send(["SAVE"]) 256 | } 257 | 258 | ///| 259 | pub async fn RedisClient::shutdown(self : RedisClient) -> RedisValue { 260 | self.send(["SHUTDOWN"]) 261 | } 262 | 263 | ///| 264 | pub async fn RedisClient::slaveof( 265 | self : RedisClient, 266 | host : String, 267 | port : String, 268 | ) -> RedisValue { 269 | self.send(["SLAVEOF", host, port]) 270 | } 271 | 272 | ///| 273 | pub async fn RedisClient::slowlog_get(self : RedisClient) -> RedisValue { 274 | self.send(["SLOWLOG", "GET"]) 275 | } 276 | 277 | ///| 278 | pub async fn RedisClient::slowlog_len(self : RedisClient) -> RedisValue { 279 | self.send(["SLOWLOG", "LEN"]) 280 | } 281 | 282 | ///| 283 | pub async fn RedisClient::slowlog_reset(self : RedisClient) -> RedisValue { 284 | self.send(["SLOWLOG", "RESET"]) 285 | } 286 | 287 | ///| 288 | pub async fn RedisClient::sync(self : RedisClient) -> RedisValue { 289 | self.send(["SYNC"]) 290 | } 291 | 292 | ///| 293 | pub async fn RedisClient::time(self : RedisClient) -> RedisValue { 294 | self.send(["TIME"]) 295 | } 296 | -------------------------------------------------------------------------------- /stream.mbt: -------------------------------------------------------------------------------- 1 | // ============ 流命令 ============ 2 | 3 | ///| 4 | /// 添加条目到流 5 | pub async fn RedisClient::xadd( 6 | self : RedisClient, 7 | key : String, 8 | id : String, 9 | field_values : Map[String, String], 10 | ) -> RedisString { 11 | let values = field_values 12 | .map(fn(k, v) -> Array[String] { [k, v] }) 13 | .values() 14 | .to_array() 15 | .flatten() 16 | let args = ["XADD", key, id, ..values] 17 | self.send(args).to_string() 18 | } 19 | 20 | ///| 21 | /// 读取流条目 22 | pub async fn RedisClient::xread( 23 | self : RedisClient, 24 | count? : Int, 25 | block? : Int, 26 | streams : Array[(String, String)], 27 | ) -> RedisStreamResult { 28 | let args = ["XREAD"] 29 | if count is Some(c) { 30 | args.append(["COUNT", c.to_string()]) 31 | } 32 | if block is Some(b) { 33 | args.append(["BLOCK", b.to_string()]) 34 | } 35 | let (keys, ids) = streams.unzip() 36 | args.append(["STREAMS", ..keys, ..ids]) 37 | self.send(args).to_stream() 38 | } 39 | 40 | ///| 41 | /// 范围查询流条目 42 | pub async fn RedisClient::xrange( 43 | self : RedisClient, 44 | key : String, 45 | start~ : String, 46 | end~ : String, 47 | count? : Int, 48 | ) -> RedisStreamMessageArray { 49 | let args = ["XRANGE", key, start, end] 50 | if count is Some(c) { 51 | args.append(["COUNT", c.to_string()]) 52 | } 53 | self.send(args).to_stream_message_array() 54 | } 55 | 56 | ///| 57 | /// 反向范围查询流条目 58 | pub async fn RedisClient::xrevrange( 59 | self : RedisClient, 60 | key : String, 61 | end~ : String, 62 | start~ : String, 63 | count? : Int, 64 | ) -> RedisStreamMessageArray { 65 | let args = ["XREVRANGE", key, end, start] 66 | if count is Some(c) { 67 | args.append(["COUNT", c.to_string()]) 68 | } 69 | self.send(args).to_stream_message_array() 70 | } 71 | 72 | ///| 73 | /// 获取流长度 74 | pub async fn RedisClient::xlen(self : RedisClient, key : String) -> RedisInt { 75 | self.send(["XLEN", key]).to_int() 76 | } 77 | 78 | ///| 79 | /// 删除流条目 80 | pub async fn RedisClient::xdel( 81 | self : RedisClient, 82 | key : String, 83 | ids : Array[String], 84 | ) -> RedisInt { 85 | let args = ["XDEL", key, ..ids] 86 | self.send(args).to_int() 87 | } 88 | 89 | ///| 90 | pub(all) enum XTrimStrategy { 91 | MaxLen 92 | MinID 93 | } 94 | 95 | ///| 96 | impl Show for XTrimStrategy with output(self, logger) { 97 | logger.write_string(self.to_string()) 98 | } 99 | 100 | ///| 101 | impl Show for XTrimStrategy with to_string(self) { 102 | match self { 103 | MaxLen => "MAXLEN" 104 | MinID => "MINID" 105 | } 106 | } 107 | 108 | ///| 109 | /// 修剪流 110 | pub async fn RedisClient::xtrim( 111 | self : RedisClient, 112 | key : String, 113 | strategy~ : XTrimStrategy, 114 | threshold~ : Int64, 115 | approximate? : Bool = false, 116 | limit? : Int, 117 | keepref? : Bool = false, 118 | delref? : Bool = false, 119 | acked? : Bool = false, 120 | ) -> RedisInt { 121 | let args = ["XTRIM", key, strategy.to_string()] 122 | if approximate { 123 | args.push("~") 124 | } 125 | args.push(threshold.to_string()) 126 | if limit is Some(l) { 127 | args.append(["LIMIT", l.to_string()]) 128 | } 129 | if keepref { 130 | args.push("KEEPREF") 131 | } 132 | if delref { 133 | args.push("DELREF") 134 | } 135 | if acked { 136 | args.push("ACKED") 137 | } 138 | self.send(args).to_int() 139 | } 140 | 141 | ///| 142 | /// 创建消费者组 143 | pub async fn RedisClient::xgroup_create( 144 | self : RedisClient, 145 | key : String, 146 | group : String, 147 | id : String, 148 | mkstream : Bool, 149 | ) -> RedisString { 150 | let args = ["XGROUP", "CREATE", key, group, id] 151 | if mkstream { 152 | args.push("MKSTREAM") 153 | } 154 | self.send(args).to_string() 155 | } 156 | 157 | ///| 158 | /// 销毁消费者组 159 | pub async fn RedisClient::xgroup_destroy( 160 | self : RedisClient, 161 | key : String, 162 | group : String, 163 | ) -> RedisInt { 164 | self.send(["XGROUP", "DESTROY", key, group]).to_int() 165 | } 166 | 167 | ///| 168 | /// 创建消费者 169 | pub async fn RedisClient::xgroup_createconsumer( 170 | self : RedisClient, 171 | key : String, 172 | group : String, 173 | consumer : String, 174 | ) -> RedisInt { 175 | self.send(["XGROUP", "CREATECONSUMER", key, group, consumer]).to_int() 176 | } 177 | 178 | ///| 179 | /// 删除消费者 180 | pub async fn RedisClient::xgroup_delconsumer( 181 | self : RedisClient, 182 | key : String, 183 | group : String, 184 | consumer : String, 185 | ) -> RedisInt { 186 | self.send(["XGROUP", "DELCONSUMER", key, group, consumer]).to_int() 187 | } 188 | 189 | ///| 190 | /// 设置消费者组ID 191 | pub async fn RedisClient::xgroup_setid( 192 | self : RedisClient, 193 | key : String, 194 | group : String, 195 | id : String, 196 | ) -> RedisString { 197 | self.send(["XGROUP", "SETID", key, group, id]).to_string() 198 | } 199 | 200 | ///| 201 | /// 消费者组读取 202 | pub async fn RedisClient::xreadgroup( 203 | self : RedisClient, 204 | group : String, 205 | consumer : String, 206 | count? : Int, 207 | block? : Int, 208 | noack? : Bool = false, 209 | streams : Array[(String, String)], 210 | ) -> RedisResult[RedisStream] { 211 | let args = ["XREADGROUP", "GROUP", group, consumer] 212 | if count is Some(c) { 213 | args.append(["COUNT", c.to_string()]) 214 | } 215 | if block is Some(b) { 216 | args.append(["BLOCK", b.to_string()]) 217 | } 218 | if noack { 219 | args.push("NOACK") 220 | } 221 | let (keys, ids) = streams.unzip() 222 | args.append(["STREAMS", ..keys, ..ids]) 223 | self.send(args).to_stream() 224 | } 225 | 226 | ///| 227 | /// 确认消息 228 | pub async fn RedisClient::xack( 229 | self : RedisClient, 230 | key : String, 231 | group : String, 232 | ids : Array[String], 233 | ) -> RedisInt { 234 | let args = ["XACK", key, group, ..ids] 235 | self.send(args).to_int() 236 | } 237 | 238 | ///| 239 | /// 查看待处理消息 240 | pub async fn RedisClient::xpending( 241 | self : RedisClient, 242 | key : String, 243 | group : String, 244 | ) -> RedisArray { 245 | self.send(["XPENDING", key, group]).to_array() 246 | } 247 | 248 | ///| 249 | /// 查看待处理消息详情 250 | pub async fn RedisClient::xpending_range( 251 | self : RedisClient, 252 | key : String, 253 | group : String, 254 | start~ : String, 255 | end~ : String, 256 | count~ : Int, 257 | consumer? : String, 258 | ) -> RedisArray { 259 | let args = ["XPENDING", key, group, start, end, count.to_string()] 260 | if consumer is Some(c) { 261 | args.push(c) 262 | } 263 | self.send(args).to_array() 264 | } 265 | 266 | ///| 267 | /// 声明消息 268 | pub async fn RedisClient::xclaim( 269 | self : RedisClient, 270 | key : String, 271 | group : String, 272 | consumer : String, 273 | min_idle_time : Int, 274 | ids : Array[String], 275 | ) -> RedisArray { 276 | let args = ["XCLAIM", key, group, consumer, min_idle_time.to_string(), ..ids] 277 | self.send(args).to_array() 278 | } 279 | 280 | ///| 281 | /// 自动声明消息 282 | pub async fn RedisClient::xautoclaim( 283 | self : RedisClient, 284 | key : String, 285 | group : String, 286 | consumer : String, 287 | min_idle_time : Int, 288 | start~ : String, 289 | count? : Int, 290 | ) -> RedisArray { 291 | let args = [ 292 | "XAUTOCLAIM", 293 | key, 294 | group, 295 | consumer, 296 | min_idle_time.to_string(), 297 | start, 298 | ] 299 | if count is Some(c) { 300 | args.append(["COUNT", c.to_string()]) 301 | } 302 | self.send(args).to_array() 303 | } 304 | 305 | ///| 306 | /// 获取流信息 307 | pub async fn RedisClient::xinfo_stream( 308 | self : RedisClient, 309 | key : String, 310 | ) -> RedisArray { 311 | self.send(["XINFO", "STREAM", key]).to_array() 312 | } 313 | 314 | ///| 315 | /// 获取消费者组信息 316 | pub async fn RedisClient::xinfo_groups( 317 | self : RedisClient, 318 | key : String, 319 | ) -> RedisArray { 320 | self.send(["XINFO", "GROUPS", key]).to_array() 321 | } 322 | 323 | ///| 324 | /// 获取消费者信息 325 | pub async fn RedisClient::xinfo_consumers( 326 | self : RedisClient, 327 | key : String, 328 | group : String, 329 | ) -> RedisArray { 330 | self.send(["XINFO", "CONSUMERS", key, group]).to_array() 331 | } 332 | -------------------------------------------------------------------------------- /sorted_set.mbt: -------------------------------------------------------------------------------- 1 | // ============ 有序集合命令 ============ 2 | 3 | ///| 4 | pub(all) struct ZAddMember { 5 | score : String 6 | value : String 7 | } 8 | 9 | ///| 10 | pub async fn RedisClient::zadd( 11 | self : RedisClient, 12 | key : String, 13 | zadd_members : Array[ZAddMember], 14 | ) -> RedisInt { 15 | let args = [ 16 | "ZADD", 17 | key, 18 | ..zadd_members.map(m => [m.score, m.value]).flatten(), 19 | ] 20 | self.send(args).to_int() 21 | } 22 | 23 | ///| 24 | pub async fn RedisClient::zrem( 25 | self : RedisClient, 26 | key : String, 27 | members : Array[String], 28 | ) -> RedisInt { 29 | let args = ["ZREM", key, ..members] 30 | self.send(args).to_int() 31 | } 32 | 33 | ///| 34 | pub async fn RedisClient::zrange( 35 | self : RedisClient, 36 | key : String, 37 | start~ : String, 38 | stop~ : String, 39 | ) -> RedisArray { 40 | self.send(["ZRANGE", key, start, stop]).to_array() 41 | } 42 | 43 | ///| 44 | pub async fn RedisClient::zrangebyscore( 45 | self : RedisClient, 46 | key : String, 47 | min~ : String, 48 | max~ : String, 49 | ) -> RedisArray { 50 | self.send(["ZRANGEBYSCORE", key, min, max]).to_array() 51 | } 52 | 53 | ///| 54 | pub async fn RedisClient::zrangebylex( 55 | self : RedisClient, 56 | key : String, 57 | min~ : String, 58 | max~ : String, 59 | ) -> RedisArray { 60 | self.send(["ZRANGEBYLEX", key, min, max]).to_array() 61 | } 62 | 63 | ///| 64 | pub async fn RedisClient::zrevrange( 65 | self : RedisClient, 66 | key : String, 67 | start~ : String, 68 | stop~ : String, 69 | ) -> RedisArray { 70 | self.send(["ZREVRANGE", key, start, stop]).to_array() 71 | } 72 | 73 | ///| 74 | pub async fn RedisClient::zrevrangebyscore( 75 | self : RedisClient, 76 | key : String, 77 | max~ : String, 78 | min~ : String, 79 | ) -> RedisArray { 80 | self.send(["ZREVRANGEBYSCORE", key, max, min]).to_array() 81 | } 82 | 83 | ///| 84 | pub async fn RedisClient::zrevrangebylex( 85 | self : RedisClient, 86 | key : String, 87 | max~ : String, 88 | min~ : String, 89 | ) -> RedisArray { 90 | self.send(["ZREVRANGEBYLEX", key, max, min]).to_array() 91 | } 92 | 93 | ///| 94 | pub async fn RedisClient::zrangestore( 95 | self : RedisClient, 96 | dst : String, 97 | src : String, 98 | min~ : String, 99 | max~ : String, 100 | ) -> RedisInt { 101 | self.send(["ZRANGESTORE", dst, src, min, max]).to_int() 102 | } 103 | 104 | ///| 105 | pub async fn RedisClient::zscore( 106 | self : RedisClient, 107 | key : String, 108 | value : String, 109 | ) -> RedisString { 110 | self.send(["ZSCORE", key, value]).to_string() 111 | } 112 | 113 | ///| 114 | pub async fn RedisClient::zmscore( 115 | self : RedisClient, 116 | key : String, 117 | members : Array[String], 118 | ) -> RedisArray { 119 | let args = ["ZMSCORE", key, ..members] 120 | self.send(args).to_array() 121 | } 122 | 123 | ///| 124 | pub async fn RedisClient::zcard(self : RedisClient, key : String) -> RedisInt { 125 | self.send(["ZCARD", key]).to_int() 126 | } 127 | 128 | ///| 129 | pub async fn RedisClient::zcount( 130 | self : RedisClient, 131 | key : String, 132 | min~ : String, 133 | max~ : String, 134 | ) -> RedisInt { 135 | self.send(["ZCOUNT", key, min, max]).to_int() 136 | } 137 | 138 | ///| 139 | pub async fn RedisClient::zlexcount( 140 | self : RedisClient, 141 | key : String, 142 | min~ : String, 143 | max~ : String, 144 | ) -> RedisInt { 145 | self.send(["ZLEXCOUNT", key, min, max]).to_int() 146 | } 147 | 148 | ///| 149 | pub async fn RedisClient::zrank( 150 | self : RedisClient, 151 | key : String, 152 | value : String, 153 | ) -> RedisInt { 154 | self.send(["ZRANK", key, value]).to_int() 155 | } 156 | 157 | ///| 158 | pub async fn RedisClient::zrevrank( 159 | self : RedisClient, 160 | key : String, 161 | value : String, 162 | ) -> RedisInt { 163 | self.send(["ZREVRANK", key, value]).to_int() 164 | } 165 | 166 | ///| 167 | pub async fn RedisClient::zincrby( 168 | self : RedisClient, 169 | key : String, 170 | increment~ : String, 171 | value~ : String, 172 | ) -> RedisString { 173 | self.send(["ZINCRBY", key, increment, value]).to_string() 174 | } 175 | 176 | ///| 177 | pub async fn RedisClient::zpopmax( 178 | self : RedisClient, 179 | key : String, 180 | ) -> RedisString { 181 | self.send(["ZPOPMAX", key]).to_string() 182 | } 183 | 184 | ///| 185 | pub async fn RedisClient::zpopmin( 186 | self : RedisClient, 187 | key : String, 188 | ) -> RedisString { 189 | self.send(["ZPOPMIN", key]).to_string() 190 | } 191 | 192 | ///| 193 | pub async fn RedisClient::bzpopmax( 194 | self : RedisClient, 195 | keys : Array[String], 196 | timeout : String, 197 | ) -> RedisString { 198 | let args = ["BZPOPMAX", ..keys, timeout] 199 | self.send(args).to_string() 200 | } 201 | 202 | ///| 203 | pub async fn RedisClient::bzpopmin( 204 | self : RedisClient, 205 | keys : Array[String], 206 | timeout : String, 207 | ) -> RedisString { 208 | let args = ["BZPOPMIN", ..keys, timeout] 209 | self.send(args).to_string() 210 | } 211 | 212 | ///| 213 | pub async fn RedisClient::zrandmember( 214 | self : RedisClient, 215 | key : String, 216 | ) -> RedisString { 217 | self.send(["ZRANDMEMBER", key]).to_string() 218 | } 219 | 220 | ///| 221 | pub async fn RedisClient::zremrangebyrank( 222 | self : RedisClient, 223 | key : String, 224 | start : String, 225 | stop : String, 226 | ) -> RedisInt { 227 | self.send(["ZREMRANGEBYRANK", key, start, stop]).to_int() 228 | } 229 | 230 | ///| 231 | pub async fn RedisClient::zremrangebyscore( 232 | self : RedisClient, 233 | key : String, 234 | min : String, 235 | max : String, 236 | ) -> RedisInt { 237 | self.send(["ZREMRANGEBYSCORE", key, min, max]).to_int() 238 | } 239 | 240 | ///| 241 | pub async fn RedisClient::zremrangebylex( 242 | self : RedisClient, 243 | key : String, 244 | min : String, 245 | max : String, 246 | ) -> RedisInt { 247 | self.send(["ZREMRANGEBYLEX", key, min, max]).to_int() 248 | } 249 | 250 | ///| 251 | pub async fn RedisClient::zunion( 252 | self : RedisClient, 253 | numkeys : String, 254 | keys : Array[String], 255 | ) -> RedisArray { 256 | let args = ["ZUNION", numkeys, ..keys] 257 | self.send(args).to_array() 258 | } 259 | 260 | ///| 261 | pub async fn RedisClient::zunionstore( 262 | self : RedisClient, 263 | destination : String, 264 | numkeys : String, 265 | keys : Array[String], 266 | ) -> RedisInt { 267 | let args = ["ZUNIONSTORE", destination, numkeys, ..keys] 268 | self.send(args).to_int() 269 | } 270 | 271 | ///| 272 | pub async fn RedisClient::zinter( 273 | self : RedisClient, 274 | numkeys : String, 275 | keys : Array[String], 276 | ) -> RedisArray { 277 | let args = ["ZINTER", numkeys, ..keys] 278 | self.send(args).to_array() 279 | } 280 | 281 | ///| 282 | pub async fn RedisClient::zintercard( 283 | self : RedisClient, 284 | numkeys : String, 285 | keys : Array[String], 286 | ) -> RedisInt { 287 | let args = ["ZINTERCARD", numkeys, ..keys] 288 | self.send(args).to_int() 289 | } 290 | 291 | ///| 292 | pub async fn RedisClient::zinterstore( 293 | self : RedisClient, 294 | destination : String, 295 | numkeys : String, 296 | keys : Array[String], 297 | ) -> RedisInt { 298 | let args = ["ZINTERSTORE", destination, numkeys, ..keys] 299 | self.send(args).to_int() 300 | } 301 | 302 | ///| 303 | pub async fn RedisClient::zdiff( 304 | self : RedisClient, 305 | numkeys : String, 306 | keys : Array[String], 307 | ) -> RedisArray { 308 | let args = ["ZDIFF", numkeys, ..keys] 309 | self.send(args).to_array() 310 | } 311 | 312 | ///| 313 | pub async fn RedisClient::zdiffstore( 314 | self : RedisClient, 315 | destination : String, 316 | numkeys : String, 317 | keys : Array[String], 318 | ) -> RedisInt { 319 | let args = ["ZDIFFSTORE", destination, numkeys, ..keys] 320 | self.send(args).to_int() 321 | } 322 | 323 | ///| 324 | pub async fn RedisClient::zmpop( 325 | self : RedisClient, 326 | numkeys : String, 327 | keys : Array[String], 328 | value : String, 329 | ) -> RedisArray { 330 | let args = ["ZMPOP", numkeys, ..keys, value] 331 | self.send(args).to_array() 332 | } 333 | 334 | ///| 335 | pub async fn RedisClient::bzmpop( 336 | self : RedisClient, 337 | timeout : String, 338 | numkeys : String, 339 | keys : Array[String], 340 | where_ : String, 341 | count : String?, // optional COUNT argument 342 | ) -> RedisArray { 343 | let args = match count { 344 | Some(c) => ["BZMPOP", timeout, numkeys, ..keys, where_, "COUNT", c] 345 | None => ["BZMPOP", timeout, numkeys, ..keys, where_] 346 | } 347 | self.send(args).to_array() 348 | } 349 | 350 | ///| 351 | pub async fn RedisClient::zscan( 352 | self : RedisClient, 353 | key : String, 354 | cursor : String, 355 | ) -> RedisArray { 356 | self.send(["ZSCAN", key, cursor]).to_array() 357 | } 358 | -------------------------------------------------------------------------------- /json_test.mbt: -------------------------------------------------------------------------------- 1 | // ============ JSON 命令测试 ============ 2 | // 需要 Redis 服务器 6.2 或以上版本 才能运行 JSON 命令 3 | 4 | ///| 5 | /// 测试基础 JSON 操作:SET, GET, DEL, TYPE 6 | async test "json basic operations" { 7 | @async.with_task_group(fn(_root) { 8 | let client = connect("localhost", 6379) 9 | defer client.close() 10 | 11 | // 测试 JSON.SET 和 JSON.GET 12 | let set_result = client.json_set( 13 | "user:1", "$", "{\"name\":\"Alice\",\"age\":30}", 14 | ) 15 | inspect(set_result, content="OK") 16 | let get_result = client.json_get("user:1", "$") 17 | inspect(get_result, content="[{\"name\":\"Alice\",\"age\":30}]") 18 | 19 | // 测试获取特定路径 20 | let name_result = client.json_get("user:1", "$.name") 21 | inspect(name_result, content="[\"Alice\"]") 22 | let age_result = client.json_get("user:1", "$.age") 23 | inspect(age_result, content="[30]") 24 | 25 | // 测试 JSON.TYPE 26 | let type_result = client.json_type("user:1", "$") 27 | inspect(type_result, content="object") 28 | let name_type = client.json_type("user:1", "$.name") 29 | inspect(name_type, content="string") 30 | let age_type = client.json_type("user:1", "$.age") 31 | inspect(age_type, content="integer") 32 | 33 | // 测试 JSON.DEL 34 | let del_result = client.json_del("user:1", "$.age") 35 | inspect(del_result, content="1") 36 | 37 | // 验证删除后的结果 38 | let after_del = client.json_get("user:1", "$") 39 | inspect(after_del, content="[{\"name\":\"Alice\"}]") 40 | 41 | // 清理 42 | let _ = client.json_del("user:1", "$") 43 | 44 | }) 45 | } 46 | 47 | ///| 48 | /// 测试 JSON 数组操作 49 | async test "json array operations" { 50 | @async.with_task_group(fn(_root) { 51 | let client = connect("localhost", 6379) 52 | defer client.close() 53 | 54 | // 设置初始数组 55 | let _ = client.json_set("arr:1", "$", "[1,2,3]") 56 | 57 | // 测试 JSON.ARRLEN 58 | let len_result = client.json_arrlen("arr:1", "$") 59 | inspect(len_result, content="3") 60 | 61 | // 测试 JSON.ARRAPPEND 62 | let append_result = client.json_arrappend("arr:1", "$", ["4", "5"]) 63 | inspect(append_result, content="5") 64 | let after_append = client.json_get("arr:1", "$") 65 | inspect(after_append, content="[1,2,3,4,5]") 66 | 67 | // 测试 JSON.ARRINSERT 68 | let insert_result = client.json_arrinsert("arr:1", "$", 2, ["\"inserted\""]) 69 | inspect(insert_result, content="6") 70 | let after_insert = client.json_get("arr:1", "$") 71 | inspect(after_insert, content="[1,2,\"inserted\",3,4,5]") 72 | 73 | // 测试 JSON.ARRPOP 74 | let pop_result = client.json_arrpop("arr:1", "$", -1) 75 | inspect(pop_result, content="5") 76 | let after_pop = client.json_get("arr:1", "$") 77 | inspect(after_pop, content="[1,2,\"inserted\",3,4]") 78 | 79 | // 测试 JSON.ARRTRIM 80 | let trim_result = client.json_arrtrim("arr:1", "$", 1, 3) 81 | inspect(trim_result, content="3") 82 | let after_trim = client.json_get("arr:1", "$") 83 | inspect(after_trim, content="[2,\"inserted\",3]") 84 | 85 | // 清理 86 | let _ = client.json_del("arr:1", "$") 87 | 88 | }) 89 | } 90 | 91 | ///| 92 | /// 测试 JSON 对象操作 93 | async test "json object operations" { 94 | @async.with_task_group(fn(_root) { 95 | let client = connect("localhost", 6379) 96 | defer client.close() 97 | 98 | // 设置初始对象 99 | let _ = client.json_set( 100 | "obj:1", "$", "{\"name\":\"Bob\",\"age\":25,\"city\":\"NYC\"}", 101 | ) 102 | 103 | // 测试 JSON.OBJKEYS 104 | let keys_result = client.json_objkeys("obj:1", "$") 105 | inspect(keys_result, content="[\"name\",\"age\",\"city\"]") 106 | 107 | // 测试 JSON.OBJLEN 108 | let len_result = client.json_objlen("obj:1", "$") 109 | inspect(len_result, content="3") 110 | 111 | // 添加新字段 112 | let _ = client.json_set("obj:1", "$.country", "\"USA\"") 113 | let updated_keys = client.json_objkeys("obj:1", "$") 114 | inspect(updated_keys, content="[\"name\",\"age\",\"city\",\"country\"]") 115 | let updated_len = client.json_objlen("obj:1", "$") 116 | inspect(updated_len, content="4") 117 | 118 | // 清理 119 | let _ = client.json_del("obj:1", "$") 120 | 121 | }) 122 | } 123 | 124 | ///| 125 | /// 测试 JSON 数值操作 126 | async test "json numeric operations" { 127 | @async.with_task_group(fn(_root) { 128 | let client = connect("localhost", 6379) 129 | defer client.close() 130 | 131 | // 设置初始数值 132 | let _ = client.json_set("num:1", "$", "{\"counter\":10,\"price\":99.99}") 133 | 134 | // 测试 JSON.NUMINCRBY 135 | let incr_result = client.json_numincrby("num:1", "$.counter", "5") 136 | inspect(incr_result, content="15") 137 | let after_incr = client.json_get("num:1", "$.counter") 138 | inspect(after_incr, content="[15]") 139 | 140 | // 测试 JSON.NUMMULTBY 141 | let mult_result = client.json_nummultby("num:1", "$.price", "1.1") 142 | inspect(mult_result, content="109.989") 143 | let after_mult = client.json_get("num:1", "$.price") 144 | inspect(after_mult, content="[109.989]") 145 | 146 | // 清理 147 | let _ = client.json_del("num:1", "$") 148 | 149 | }) 150 | } 151 | 152 | ///| 153 | /// 测试 JSON 字符串操作 154 | async test "json string operations" { 155 | @async.with_task_group(fn(_root) { 156 | let client = connect("localhost", 6379) 157 | defer client.close() 158 | 159 | // 设置初始字符串 160 | let _ = client.json_set("str:1", "$", "{\"message\":\"Hello\"}") 161 | 162 | // 测试 JSON.STRLEN 163 | let len_result = client.json_strlen("str:1", "$.message") 164 | inspect(len_result, content="5") 165 | 166 | // 测试 JSON.STRAPPEND 167 | let append_result = client.json_strappend( 168 | "str:1", "$.message", "\" World\"", 169 | ) 170 | inspect(append_result, content="11") 171 | let after_append = client.json_get("str:1", "$.message") 172 | inspect(after_append, content="[\"Hello World\"]") 173 | 174 | // 验证长度变化 175 | let new_len = client.json_strlen("str:1", "$.message") 176 | inspect(new_len, content="11") 177 | 178 | // 清理 179 | let _ = client.json_del("str:1", "$") 180 | 181 | }) 182 | } 183 | 184 | ///| 185 | /// 测试 JSON 布尔值和其他操作 186 | async test "json boolean and misc operations" { 187 | @async.with_task_group(fn(_root) { 188 | let client = connect("localhost", 6379) 189 | defer client.close() 190 | 191 | // 设置初始布尔值 192 | let _ = client.json_set( 193 | "bool:1", "$", "{\"active\":true,\"verified\":false}", 194 | ) 195 | 196 | // 测试 JSON.TOGGLE 197 | let toggle_result = client.json_toggle("bool:1", "$.active") 198 | inspect(toggle_result, content="1") 199 | let after_toggle = client.json_get("bool:1", "$.active") 200 | inspect(after_toggle, content="[false]") 201 | 202 | // 再次切换 203 | let _ = client.json_toggle("bool:1", "$.verified") 204 | let verified_after = client.json_get("bool:1", "$.verified") 205 | inspect(verified_after, content="[true]") 206 | 207 | // 测试 JSON.CLEAR 208 | let _ = client.json_set( 209 | "clear:1", "$", "{\"data\":[1,2,3],\"info\":{\"a\":1}}", 210 | ) 211 | let clear_result = client.json_clear("clear:1", "$.data") 212 | inspect(clear_result, content="1") 213 | let after_clear = client.json_get("clear:1", "$.data") 214 | inspect(after_clear, content="[[]]") 215 | 216 | // 测试 JSON.FORGET (JSON.DEL 的别名) 217 | let forget_result = client.json_forget("bool:1", "$.active") 218 | inspect(forget_result, content="1") 219 | let after_forget = client.json_get("bool:1", "$") 220 | inspect(after_forget, content="[{\"verified\":true}]") 221 | 222 | // 清理 223 | let _ = client.json_del("bool:1", "$") 224 | let _ = client.json_del("clear:1", "$") 225 | 226 | }) 227 | } 228 | 229 | ///| 230 | /// 测试错误情况和边界条件 231 | async test "json error cases and edge conditions" { 232 | @async.with_task_group(fn(_root) { 233 | let client = connect("localhost", 6379) 234 | defer client.close() 235 | 236 | // 测试不存在的键 237 | let nonexistent = client.json_get("nonexistent:key", "$") 238 | inspect(nonexistent, content="null") 239 | 240 | // 测试无效路径 241 | let _ = client.json_set("edge:1", "$", "{\"valid\":\"data\"}") 242 | let invalid_path = client.json_get("edge:1", "$.invalid") 243 | inspect(invalid_path, content="[]") 244 | 245 | // 测试删除不存在的路径 246 | let del_invalid = client.json_del("edge:1", "$.nonexistent") 247 | inspect(del_invalid, content="0") 248 | 249 | // 测试空数组操作 250 | let _ = client.json_set("edge:2", "$", "[]") 251 | let empty_len = client.json_arrlen("edge:2", "$") 252 | inspect(empty_len, content="0") 253 | 254 | // 测试空对象操作 255 | let _ = client.json_set("edge:3", "$", "{}") 256 | let empty_obj_len = client.json_objlen("edge:3", "$") 257 | inspect(empty_obj_len, content="0") 258 | let empty_keys = client.json_objkeys("edge:3", "$") 259 | inspect(empty_keys, content="[]") 260 | 261 | // 清理 262 | let _ = client.json_del("edge:1", "$") 263 | let _ = client.json_del("edge:2", "$") 264 | let _ = client.json_del("edge:3", "$") 265 | 266 | }) 267 | } 268 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /types.mbt: -------------------------------------------------------------------------------- 1 | // ============ Redis 响应类型系统 ============ 2 | 3 | ///| 4 | /// Redis响应的基本类型 5 | pub(all) enum RedisValue { 6 | // 简单字符串: +OK\r\n 7 | SimpleString(String) 8 | // 错误: -ERR message\r\n 9 | Error(String) 10 | // 整数: :1000\r\n 11 | Integer(Int) 12 | // 批量字符串: $6\r\nfoobar\r\n 或 $-1\r\n (null) 13 | BulkString(String?) 14 | // 数组: *count\r\n... 15 | Array(Array[RedisValue]) 16 | } derive(Show, Hash, Eq) 17 | 18 | ///| 19 | /// Redis命令的结果类型 20 | pub typealias Result[T, String] as RedisResult[T] 21 | 22 | ///| 23 | /// 常用的具体返回类型 24 | pub typealias RedisResult[String] as RedisString 25 | 26 | ///| 27 | pub typealias RedisResult[Json] as RedisJson 28 | 29 | ///| 30 | pub typealias RedisResult[String?] as RedisBulkString 31 | 32 | ///| 33 | pub typealias RedisResult[Int] as RedisInt 34 | 35 | ///| 36 | pub typealias RedisResult[Bool] as RedisBool 37 | 38 | ///| 39 | pub typealias RedisResult[Array[String]] as RedisArray 40 | 41 | ///| 42 | pub typealias RedisResult[Map[String, String]] as RedisHash 43 | 44 | ///| 45 | pub typealias RedisResult[RedisStream] as RedisStreamResult 46 | 47 | ///| 48 | pub typealias RedisResult[Array[RedisStreamMessage]] as RedisStreamMessageArray 49 | 50 | ///| 51 | pub(all) struct RedisStreamMessage { 52 | id : String 53 | fields : Array[String] 54 | } derive(Show) 55 | 56 | ///| 57 | pub(all) struct RedisStream { 58 | name : String 59 | messages : Array[RedisStreamMessage] 60 | } derive(Show) 61 | 62 | ///| 63 | pub fn RedisValue::to_stream(self : RedisValue) -> RedisStreamResult { 64 | match self { 65 | Array(streams) => 66 | // Redis Stream 响应格式: [[stream_name, [[id, [field, value, ...]], ...]]] 67 | match streams[0] { 68 | Array(stream_data) => 69 | if stream_data.length() >= 2 { 70 | let stream_name = match stream_data[0].to_string() { 71 | Ok(name) => name 72 | Err(_) => "" 73 | } 74 | let messages = match stream_data[1] { 75 | Array(msg_array) => parse_stream_messages(msg_array) 76 | _ => [] 77 | } 78 | Ok({ name: stream_name, messages }) 79 | } else { 80 | Err("Invalid stream data: expected array with at least 2 elements") 81 | } 82 | _ => Err("Invalid stream data: expected array") 83 | } 84 | _ => Err("Invalid stream data: expected array") 85 | } 86 | } 87 | 88 | ///| 89 | /// 解析 Stream 消息数组 90 | fn parse_stream_messages( 91 | msg_array : Array[RedisValue], 92 | ) -> Array[RedisStreamMessage] { 93 | let messages = Array::new() 94 | for msg in msg_array { 95 | match msg { 96 | Array(msg_data) => 97 | if msg_data.length() >= 2 { 98 | let id = match msg_data[0].to_string() { 99 | Ok(id_str) => id_str 100 | Err(_) => "" 101 | } 102 | let fields = match msg_data[1] { 103 | Array(field_array) => { 104 | let field_strings = Array::new() 105 | for field in field_array { 106 | match field.to_string() { 107 | Ok(field_str) => field_strings.push(field_str) 108 | Err(_) => () 109 | } 110 | } 111 | field_strings 112 | } 113 | _ => [] 114 | } 115 | messages.push({ id, fields }) 116 | } 117 | _ => () 118 | } 119 | } 120 | messages 121 | } 122 | 123 | ///| 124 | /// 将RedisValue转换为Stream消息数组(用于XRANGE等命令) 125 | pub fn RedisValue::to_stream_message_array( 126 | self : RedisValue, 127 | ) -> RedisStreamMessageArray { 128 | match self { 129 | Array(msg_array) => Ok(parse_stream_messages(msg_array)) 130 | _ => Err("Invalid stream message array") 131 | } 132 | } 133 | 134 | ///| 135 | /// 将RedisValue转换为具体类型 136 | pub fn RedisValue::to_bulk_string(self : RedisValue) -> RedisBulkString { 137 | match self { 138 | BulkString(Some(s)) => Ok(Some(s)) 139 | Error(e) => Err(e) 140 | _ => Err("Type mismatch: expected string") 141 | } 142 | } 143 | 144 | ///| 145 | /// 将RedisValue转换为具体类型 146 | pub fn RedisValue::to_string(self : RedisValue) -> RedisString { 147 | match self { 148 | SimpleString(s) => Ok(s) 149 | BulkString(Some(s)) => Ok(s) 150 | Error(e) => Err(e) 151 | _ => Err("Type mismatch: expected string") 152 | } 153 | } 154 | 155 | ///| 156 | /// 将RedisValue转换为JSON 157 | pub fn RedisValue::to_json(self : RedisValue) -> RedisJson { 158 | self.to_string().map(s => @json.parse(s) catch { _ => Json::null() }) 159 | } 160 | 161 | ///| 162 | /// 将RedisValue转换为整数 163 | pub fn RedisValue::to_int(self : RedisValue) -> RedisInt { 164 | match self { 165 | Integer(i) => Ok(i) 166 | Error(e) => Err(e) 167 | _ => Err("Type mismatch: expected integer") 168 | } 169 | } 170 | 171 | ///| 172 | /// 将RedisValue转换为布尔值 173 | pub fn RedisValue::to_bool(self : RedisValue) -> RedisBool { 174 | match self { 175 | Integer(i) => Ok(i != 0) 176 | SimpleString(s) => Ok(s == "OK") 177 | Error(e) => Err(e) 178 | _ => Err("Type mismatch: expected boolean") 179 | } 180 | } 181 | 182 | ///| 183 | /// 将RedisValue转换为数组 184 | pub fn RedisValue::to_array(self : RedisValue) -> RedisArray { 185 | match self { 186 | Array(arr) => { 187 | let result = Array::new() 188 | for item in arr { 189 | match item.to_string() { 190 | Result::Ok(s) => result.push(s) 191 | Result::Err(_) => 192 | return Result::Err("Array contains non-string elements") 193 | } 194 | } 195 | Result::Ok(result) 196 | } 197 | Error(e) => Result::Err(e) 198 | _ => Result::Err("Type mismatch: expected array") 199 | } 200 | } 201 | 202 | ///| 203 | /// 解析Redis协议响应 204 | pub fn parse_redis_response(response : String) -> RedisValue { 205 | if response.length() == 0 { 206 | return Error("Empty response") 207 | } 208 | let first_char = response[0] 209 | match first_char { 210 | '+' => SimpleString(extract_line_content(response, 1)) 211 | '-' => Error(extract_line_content(response, 1)) 212 | ':' => { 213 | let int_str = extract_line_content(response, 1) 214 | // 简单的字符串转整数实现 215 | if (try? @strconv.parse_int(int_str)) is Ok(i) { 216 | Integer(i) 217 | } else { 218 | Error("Invalid integer: \{int_str}") 219 | } 220 | } 221 | '$' => parse_bulk_string_value(response) 222 | '*' => parse_array_value(response) 223 | _ => Error("Unknown response type") 224 | } 225 | } 226 | 227 | ///| 228 | /// 提取行内容直到\r\n 229 | fn extract_line_content(text : String, start : Int) -> String { 230 | let mut i = start 231 | let len = text.length() 232 | let mut result = "" 233 | while i < len { 234 | let char_code = text[i] 235 | if char_code == '\r'.to_int() && i + 1 < len && text[i + 1] == '\n'.to_int() { 236 | break 237 | } 238 | result = result + char_code.unsafe_to_char().to_string() 239 | i = i + 1 240 | } 241 | result 242 | } 243 | 244 | ///| 245 | /// 解析批量字符串 246 | fn parse_bulk_string_value(response : String) -> RedisValue { 247 | let length_str = extract_line_content(response, 1) 248 | 249 | // 检查null批量字符串 250 | if length_str == "-1" { 251 | return BulkString(None) 252 | } 253 | 254 | // 找到内容开始位置 255 | let mut content_start = 1 256 | while content_start < response.length() - 1 { 257 | if response[content_start] == '\r'.to_int() && 258 | response[content_start + 1] == '\n'.to_int() { 259 | content_start = content_start + 2 260 | break 261 | } 262 | content_start = content_start + 1 263 | } 264 | let content = extract_line_content(response, content_start) 265 | BulkString(Some(content)) 266 | } 267 | 268 | ///| 269 | /// 解析数组 270 | fn parse_array_value(response : String) -> RedisValue { 271 | let count_str = extract_line_content(response, 1) 272 | 273 | // 检查null数组 274 | if count_str == "-1" { 275 | return Array([]) 276 | } 277 | 278 | // 解析数组元素个数 279 | let count = match (try? @strconv.parse_int(count_str)) { 280 | Ok(n) => n 281 | Err(_) => return Error("Invalid array count: \{count_str}") 282 | } 283 | if count == 0 { 284 | return Array([]) 285 | } 286 | 287 | // 找到第一个元素的开始位置(跳过 *count\r\n) 288 | let mut pos = 1 289 | while pos < response.length() - 1 { 290 | if response[pos] == '\r'.to_int() && response[pos + 1] == '\n'.to_int() { 291 | pos = pos + 2 292 | break 293 | } 294 | pos = pos + 1 295 | } 296 | let elements = Array::new() 297 | let mut current_pos = pos 298 | 299 | // 解析每个元素 300 | for i = 0; i < count; i = i + 1 { 301 | if current_pos >= response.length() { 302 | break 303 | } 304 | 305 | // 提取当前元素的完整RESP数据 306 | let element_data = extract_element_data(response, current_pos) 307 | match element_data { 308 | Ok((element_str, next_pos)) => { 309 | // 递归调用parse_redis_response解析元素 310 | let element_value = parse_redis_response(element_str) 311 | elements.push(element_value) 312 | current_pos = next_pos 313 | } 314 | Err(err) => return Error("Failed to parse array element: \{err}") 315 | } 316 | } 317 | Array(elements) 318 | } 319 | 320 | ///| 321 | /// 从响应中提取单个元素的完整数据 322 | fn extract_element_data( 323 | response : String, 324 | start_pos : Int, 325 | ) -> Result[(String, Int), String] { 326 | if start_pos >= response.length() { 327 | return Err("Position out of bounds") 328 | } 329 | let element_type = response[start_pos] 330 | match element_type { 331 | '$' => { 332 | // 批量字符串:$length\r\ncontent\r\n 333 | let length_str = extract_line_content(response, start_pos + 1) 334 | if length_str == "-1" { 335 | // null字符串:$-1\r\n 336 | let end_pos = find_line_end(response, start_pos) + 2 337 | let element_str = try! response[start_pos:end_pos].to_string() 338 | Ok((element_str, end_pos)) 339 | } else { 340 | let length = match (try? @strconv.parse_int(length_str)) { 341 | Ok(n) => n 342 | Err(_) => return Err("Invalid bulk string length") 343 | } 344 | // 计算整个批量字符串的结束位置 345 | let length_line_end = find_line_end(response, start_pos) + 2 346 | let content_end = length_line_end + length 347 | let element_end = content_end + 2 // 加上最后的\r\n 348 | let element_str = try! response[start_pos:element_end].to_string() 349 | Ok((element_str, element_end)) 350 | } 351 | } 352 | '+' | '-' | ':' => { 353 | // 简单字符串、错误、整数:都是单行格式 354 | let line_end = find_line_end(response, start_pos) + 2 355 | let element_str = try! response[start_pos:line_end].to_string() 356 | Ok((element_str, line_end)) 357 | } 358 | '*' => 359 | // 嵌套数组:需要递归计算长度 360 | extract_array_data(response, start_pos) 361 | _ => Err("Unknown element type") 362 | } 363 | } 364 | 365 | ///| 366 | /// 提取数组数据(支持嵌套) 367 | fn extract_array_data( 368 | response : String, 369 | start_pos : Int, 370 | ) -> Result[(String, Int), String] { 371 | let count_str = extract_line_content(response, start_pos + 1) 372 | if count_str == "-1" { 373 | // null数组:*-1\r\n 374 | let end_pos = find_line_end(response, start_pos) + 2 375 | let element_str = try! response[start_pos:end_pos].to_string() 376 | return Ok((element_str, end_pos)) 377 | } 378 | let count = match (try? @strconv.parse_int(count_str)) { 379 | Ok(n) => n 380 | Err(_) => return Err("Invalid array count") 381 | } 382 | if count == 0 { 383 | // 空数组:*0\r\n 384 | let end_pos = find_line_end(response, start_pos) + 2 385 | let element_str = try! response[start_pos:end_pos].to_string() 386 | return Ok((element_str, end_pos)) 387 | } 388 | 389 | // 找到数组内容开始位置 390 | let mut current_pos = find_line_end(response, start_pos) + 2 391 | 392 | // 递归解析每个子元素来计算总长度 393 | for i = 0; i < count; i = i + 1 { 394 | match extract_element_data(response, current_pos) { 395 | Ok((_, next_pos)) => current_pos = next_pos 396 | Err(err) => return Err("Failed to parse nested element: \{err}") 397 | } 398 | } 399 | let element_str = try! response[start_pos:current_pos].to_string() 400 | Ok((element_str, current_pos)) 401 | } 402 | 403 | ///| 404 | /// 找到行结束位置(\r\n之前的位置) 405 | fn find_line_end(text : String, start : Int) -> Int { 406 | let mut i = start 407 | while i < text.length() - 1 { 408 | if text[i] == '\r'.to_int() && text[i + 1] == '\n'.to_int() { 409 | return i 410 | } 411 | i = i + 1 412 | } 413 | text.length() 414 | } 415 | -------------------------------------------------------------------------------- /stream_test.mbt: -------------------------------------------------------------------------------- 1 | ///| 2 | /// Stream命令测试套件 3 | 4 | ///| 5 | /// 测试XADD命令 - 添加条目到流 6 | async test "stream_xadd" { 7 | @async.with_task_group(fn(_root) { 8 | let client = connect("localhost", 6379) 9 | defer client.close() 10 | 11 | // 测试XADD 12 | let add_result = client.xadd("test:stream", "*", { 13 | "field1": "value1", 14 | "field2": "value2", 15 | }) 16 | assert_true(add_result.unwrap().length() > 0) // 应该返回生成的ID 17 | 18 | // 清理 19 | let _ = client.del(["test:stream"]) 20 | 21 | }) 22 | } 23 | 24 | ///| 25 | /// 测试XLEN命令 - 获取流长度 26 | async test "stream_xlen" { 27 | @async.with_task_group(fn(_root) { 28 | let client = connect("localhost", 6379) 29 | defer client.close() 30 | 31 | // 添加一些条目 32 | let _ = client.xadd("test:stream", "*", { "field1": "value1" }) 33 | let _ = client.xadd("test:stream", "*", { "field2": "value2" }) 34 | 35 | // 测试XLEN 36 | let len_result = client.xlen("test:stream") 37 | assert_true(len_result is Ok(2)) 38 | 39 | // 清理 40 | let _ = client.del(["test:stream"]) 41 | 42 | }) 43 | } 44 | 45 | ///| 46 | /// 测试XRANGE命令 - 范围查询 47 | async test "stream_xrange" { 48 | @async.with_task_group(fn(_root) { 49 | let client = connect("localhost", 6379) 50 | defer client.close() 51 | 52 | // 添加测试数据 53 | let _ = client.xadd("test:stream", "*", { "field1": "value1" }) 54 | let _ = client.xadd("test:stream", "*", { "field2": "value2" }) 55 | 56 | // 测试XRANGE 57 | let range_result = client.xrange("test:stream", start="-", end="+") 58 | assert_true(range_result is Ok([_, _])) 59 | 60 | // 测试带COUNT的XRANGE 61 | let range_count_result = client.xrange( 62 | "test:stream", 63 | start="-", 64 | end="+", 65 | count=1, 66 | ) 67 | assert_true(range_count_result is Ok([_])) 68 | 69 | // 清理 70 | let _ = client.del(["test:stream"]) 71 | 72 | }) 73 | } 74 | 75 | ///| 76 | /// 测试XREVRANGE命令 - 反向范围查询 77 | async test "stream_xrevrange" { 78 | @async.with_task_group(fn(_root) { 79 | let client = connect("localhost", 6379) 80 | defer client.close() 81 | 82 | // 清理 83 | let _ = client.del(["test:stream"]) 84 | // 添加测试数据 85 | let _ = client.xadd("test:stream", "*", { "field1": "value1" }) 86 | let _ = client.xadd("test:stream", "*", { "field2": "value2" }) 87 | 88 | // 测试XREVRANGE 89 | let revrange_result = client.xrevrange("test:stream", end="+", start="-") 90 | assert_true(revrange_result is Ok([_, _])) 91 | 92 | // 清理 93 | let _ = client.del(["test:stream"]) 94 | 95 | }) 96 | } 97 | 98 | ///| 99 | /// 测试XDEL命令 - 删除流条目 100 | async test "stream_xdel" { 101 | @async.with_task_group(fn(_root) { 102 | let client = connect("localhost", 6379) 103 | defer client.close() 104 | 105 | // 清理 106 | let _ = client.del(["test:stream"]) 107 | // 添加测试数据 108 | let id1 = client.xadd("test:stream", "*", { "field1": "value1" }).unwrap() 109 | let _ = client.xadd("test:stream", "*", { "field2": "value2" }) 110 | 111 | // 验证长度 112 | assert_true(client.xlen("test:stream") == Ok(2)) 113 | 114 | // 测试XDEL 115 | let del_result = client.xdel("test:stream", [id1]) 116 | assert_true(del_result is Ok(1)) 117 | 118 | // 验证删除后长度 119 | assert_true(client.xlen("test:stream") is Ok(1)) 120 | 121 | // 清理 122 | let _ = client.del(["test:stream"]) 123 | 124 | }) 125 | } 126 | 127 | ///| 128 | /// 测试XTRIM命令 - 修剪流 129 | async test "stream_xtrim" { 130 | @async.with_task_group(fn(_root) { 131 | let client = connect("localhost", 6379) 132 | defer client.close() 133 | 134 | // 添加多个条目 135 | let _ = client.xadd("test:stream", "*", { "field1": "value1" }) 136 | let _ = client.xadd("test:stream", "*", { "field2": "value2" }) 137 | let _ = client.xadd("test:stream", "*", { "field3": "value3" }) 138 | 139 | // 验证初始长度 140 | assert_true(client.xlen("test:stream").unwrap() == 3) 141 | 142 | // 测试XTRIM - 保留最新2个条目 143 | let trim_result = client.xtrim( 144 | "test:stream", 145 | strategy=MaxLen, 146 | threshold=2, 147 | approximate=false, 148 | ) 149 | assert_true(trim_result.unwrap() == 1) // 删除了1个条目 150 | 151 | // 验证修剪后长度 152 | assert_true(client.xlen("test:stream").unwrap() == 2) 153 | 154 | // 清理 155 | let _ = client.del(["test:stream"]) 156 | 157 | }) 158 | } 159 | 160 | ///| 161 | /// 测试消费者组基本操作 162 | async test "stream_consumer_group_basic" { 163 | @async.with_task_group(fn(_root) { 164 | let client = connect("localhost", 6379) 165 | defer client.close() 166 | 167 | // 添加测试数据 168 | let _ = client.xadd("test:stream", "*", { "field1": "value1" }) 169 | 170 | // 测试创建消费者组 171 | let create_result = client.xgroup_create( 172 | "test:stream", "test_group", "0", false, 173 | ) 174 | assert_true(create_result.unwrap() == "OK") 175 | 176 | // 测试创建消费者 177 | let create_consumer_result = client.xgroup_createconsumer( 178 | "test:stream", "test_group", "consumer1", 179 | ) 180 | assert_true(create_consumer_result.unwrap() == 1) 181 | 182 | // 测试删除消费者 183 | let del_consumer_result = client.xgroup_delconsumer( 184 | "test:stream", "test_group", "consumer1", 185 | ) 186 | assert_true(del_consumer_result.unwrap() >= 0) 187 | 188 | // 测试销毁消费者组 189 | let destroy_result = client.xgroup_destroy("test:stream", "test_group") 190 | assert_true(destroy_result.unwrap() == 1) 191 | 192 | // 清理 193 | let _ = client.del(["test:stream"]) 194 | 195 | }) 196 | } 197 | 198 | ///| 199 | /// 测试XREAD命令 - 读取流条目 200 | async test "stream_xread" { 201 | @async.with_task_group(fn(_root) { 202 | let client = connect("localhost", 6379) 203 | defer client.close() 204 | 205 | // 添加测试数据 206 | let _ = client.xadd("test:stream", "*", { "field1": "value1" }) 207 | let _ = client.xadd("test:stream", "*", { "field2": "value2" }) 208 | 209 | // 测试XREAD 210 | let read_result = client.xread(count=10, [("test:stream", "0")]) 211 | assert_true(read_result.map(i => i.messages.length() > 0).or(false)) 212 | 213 | // 清理 214 | let _ = client.del(["test:stream"]) 215 | 216 | }) 217 | } 218 | 219 | ///| 220 | /// 测试消费者组读取和确认 221 | async test "stream_consumer_group_read_ack" { 222 | @async.with_task_group(fn(_root) { 223 | let client = connect("localhost", 6379) 224 | defer client.close() 225 | 226 | // 添加测试数据 227 | let id1 = client.xadd("test:stream", "*", { "field1": "value1" }).unwrap() 228 | let _ = client.xadd("test:stream", "*", { "field2": "value2" }) 229 | 230 | // 创建消费者组 231 | let _ = client.xgroup_create("test:stream", "test_group", "0", false) 232 | 233 | // 测试XREADGROUP 234 | let readgroup_result = client.xreadgroup( 235 | "test_group", 236 | "consumer1", 237 | count=1, 238 | [("test:stream", ">")], 239 | ) 240 | assert_true(readgroup_result.map(i => i.messages.length() > 0).or(false)) 241 | 242 | // 测试XACK 243 | let ack_result = client.xack("test:stream", "test_group", [id1]) 244 | assert_true(ack_result.unwrap() >= 0) 245 | 246 | // 清理 247 | let _ = client.xgroup_destroy("test:stream", "test_group") 248 | let _ = client.del(["test:stream"]) 249 | 250 | }) 251 | } 252 | 253 | ///| 254 | /// 测试XPENDING命令 - 查看待处理消息 255 | async test "stream_xpending" { 256 | @async.with_task_group(fn(_root) { 257 | let client = connect("localhost", 6379) 258 | defer client.close() 259 | 260 | // 添加测试数据 261 | let _ = client.xadd("test:stream", "*", { "field1": "value1" }) 262 | 263 | // 创建消费者组 264 | let _ = client.xgroup_create("test:stream", "test_group", "0", false) 265 | 266 | // 读取消息但不确认 267 | let _ = client.xreadgroup("test_group", "consumer1", count=1, [ 268 | ("test:stream", ">"), 269 | ]) 270 | 271 | // 测试XPENDING 272 | let pending_result = client.xpending("test:stream", "test_group") 273 | assert_true(pending_result.unwrap().length() > 0) 274 | 275 | // 清理 276 | let _ = client.xgroup_destroy("test:stream", "test_group") 277 | let _ = client.del(["test:stream"]) 278 | 279 | }) 280 | } 281 | 282 | ///| 283 | /// 测试XINFO命令 - 获取流信息 284 | async test "stream_xinfo" { 285 | @async.with_task_group(fn(_root) { 286 | let client = connect("localhost", 6379) 287 | defer client.close() 288 | 289 | // 添加测试数据 290 | let _ = client.xadd("test:stream", "*", { "field1": "value1" }) 291 | 292 | // 测试XINFO STREAM 293 | let info_stream_result = client.xinfo_stream("test:stream") 294 | assert_true(info_stream_result.unwrap().length() > 0) 295 | 296 | // 创建消费者组 297 | let _ = client.xgroup_create("test:stream", "test_group", "0", false) 298 | 299 | // 测试XINFO GROUPS 300 | let info_groups_result = client.xinfo_groups("test:stream") 301 | assert_true(info_groups_result.unwrap().length() > 0) 302 | 303 | // 创建消费者 304 | let _ = client.xgroup_createconsumer( 305 | "test:stream", "test_group", "consumer1", 306 | ) 307 | 308 | // 测试XINFO CONSUMERS 309 | let info_consumers_result = client.xinfo_consumers( 310 | "test:stream", "test_group", 311 | ) 312 | assert_true(info_consumers_result.unwrap().length() > 0) 313 | 314 | // 清理 315 | let _ = client.xgroup_destroy("test:stream", "test_group") 316 | let _ = client.del(["test:stream"]) 317 | 318 | }) 319 | } 320 | 321 | ///| 322 | /// 测试复杂的流操作场景 323 | async test "stream_complex_scenario" { 324 | @async.with_task_group(fn(_root) { 325 | let client = connect("localhost", 6379) 326 | defer client.close() 327 | 328 | // 清理旧数据 329 | let _ = client.del(["test:orders"]) 330 | 331 | // 1. 创建流并添加多个条目 332 | let _ = client.xadd("test:orders", "*", { 333 | "user_id": "123", 334 | "product": "laptop", 335 | "amount": "1500", 336 | }) 337 | let _ = client.xadd("test:orders", "*", { 338 | "user_id": "456", 339 | "product": "mouse", 340 | "amount": "25", 341 | }) 342 | let _ = client.xadd("test:orders", "*", { 343 | "user_id": "789", 344 | "product": "keyboard", 345 | "amount": "75", 346 | }) 347 | 348 | // 2. 验证流长度 349 | assert_true(client.xlen("test:orders") is Ok(3)) 350 | 351 | // 3. 创建多个消费者组 352 | let _ = client.xgroup_create("test:orders", "payment_processor", "0", false) 353 | let _ = client.xgroup_create("test:orders", "inventory_manager", "0", false) 354 | 355 | // 4. 不同消费者组读取消息 356 | let payment_messages = client.xreadgroup( 357 | "payment_processor", 358 | "worker1", 359 | count=2, 360 | [("test:orders", ">")], 361 | ) 362 | println(payment_messages) 363 | assert_true(payment_messages is Ok(_)) 364 | let inventory_messages = client.xreadgroup( 365 | "inventory_manager", 366 | "worker1", 367 | count=2, 368 | [("test:orders", ">")], 369 | ) 370 | assert_true(inventory_messages is Ok(_)) 371 | 372 | // 5. 查看待处理消息 373 | let payment_pending = client.xpending("test:orders", "payment_processor") 374 | assert_true(payment_pending.unwrap().length() > 0) 375 | 376 | // 6. 修剪流保留最新2个条目 377 | let _ = client.xtrim("test:orders", strategy=MaxLen, threshold=2) 378 | assert_true(client.xlen("test:orders") is Ok(2)) 379 | 380 | // 7. 再次查看待处理消息,应该为空 381 | let payment_pending_after_ack = client.xpending( 382 | "test:orders", "payment_processor", 383 | ) 384 | assert_true(payment_pending_after_ack is Ok(_)) 385 | 386 | // 清理 387 | let _ = client.xgroup_destroy("test:orders", "payment_processor") 388 | let _ = client.xgroup_destroy("test:orders", "inventory_manager") 389 | let _ = client.del(["test:orders"]) 390 | 391 | }) 392 | } 393 | 394 | ///| 395 | /// 测试边界情况和错误处理 396 | async test "stream_edge_cases" { 397 | @async.with_task_group(fn(_root) { 398 | let client = connect("localhost", 6379) 399 | defer client.close() 400 | 401 | // 清理 402 | let _ = client.xgroup_destroy("test:stream", "test_group") 403 | let _ = client.del(["test:stream"]) 404 | 405 | // 测试空流的操x作 406 | let empty_len = client.xlen("nonexistent:stream") 407 | assert_true(empty_len.unwrap() == 0) 408 | let empty_range = client.xrange("nonexistent:stream", start="-", end="+") 409 | println(empty_range) 410 | assert_true(empty_range is Ok([])) 411 | 412 | // 测试删除不存在的条目 413 | let del_nonexistent = client.xdel("nonexistent:stream", ["1234567890-0"]) 414 | assert_true(del_nonexistent.unwrap() == 0) 415 | 416 | // 测试销毁不存在的消费者组 417 | let destroy_nonexistent = client.xgroup_destroy( 418 | "nonexistent:stream", "nonexistent_group", 419 | ) 420 | // 这应该返回错误,但我们只验证它不会崩溃 421 | let _ = destroy_nonexistent 422 | 423 | // 添加一个条目用于后续测试 424 | let _ = client.xadd("test:stream", "*", { "test": "value" }) 425 | 426 | // 测试重复创建消费者组 427 | let _ = client.xgroup_create("test:stream", "test_group", "0", false) 428 | let duplicate_create = client.xgroup_create( 429 | "test:stream", "test_group", "0", false, 430 | ) 431 | // 这应该返回错误,但我们只验证它不会崩溃 432 | let _ = duplicate_create 433 | 434 | // 清理 435 | let _ = client.xgroup_destroy("test:stream", "test_group") 436 | let _ = client.del(["test:stream"]) 437 | 438 | }) 439 | } 440 | -------------------------------------------------------------------------------- /pkg.generated.mbti: -------------------------------------------------------------------------------- 1 | // Generated using `moon info`, DON'T EDIT IT 2 | package "oboard/redis" 3 | 4 | import( 5 | "moonbitlang/async/socket" 6 | ) 7 | 8 | // Values 9 | async fn connect(String, Int) -> RedisClient 10 | 11 | fn parse_redis_response(String) -> RedisValue 12 | 13 | // Errors 14 | 15 | // Types and methods 16 | pub(all) enum Bit { 17 | Zero 18 | One 19 | } 20 | impl Show for Bit 21 | 22 | pub(all) enum BitFieldEncoding { 23 | U8 24 | U16 25 | U32 26 | U64 27 | I8 28 | I16 29 | I32 30 | I64 31 | } 32 | impl Show for BitFieldEncoding 33 | 34 | pub(all) enum BitFieldOperation { 35 | Get(encoding~ : BitFieldEncoding, offset~ : Int) 36 | Set(encoding~ : BitFieldEncoding, offset~ : Int, value~ : Bit) 37 | Incrby(encoding~ : BitFieldEncoding, offset~ : Int, value~ : Int) 38 | Overflow(behavior~ : String) 39 | } 40 | fn BitFieldOperation::to_array(Self) -> Array[String] 41 | 42 | pub(all) struct GeoCoordinates { 43 | longitude : Double 44 | latitude : Double 45 | } 46 | fn GeoCoordinates::to_array(Self) -> Array[String] 47 | 48 | pub(all) struct GeoSearchByBox { 49 | width : Int 50 | height : Int 51 | unit : GeoUnits 52 | } 53 | fn GeoSearchByBox::to_array(Self) -> Array[String] 54 | 55 | pub(all) enum GeoUnits { 56 | Meters 57 | Kilometers 58 | Miles 59 | Feet 60 | } 61 | impl Show for GeoUnits 62 | 63 | pub struct RedisClient { 64 | conn : @socket.TCP 65 | } 66 | async fn RedisClient::append(Self, String, String) -> Result[Int, String] 67 | async fn RedisClient::auth(Self, String) -> Result[String, String] 68 | async fn RedisClient::auth_username(Self, String, String) -> Result[String, String] 69 | async fn RedisClient::bgrewriteaof(Self) -> RedisValue 70 | async fn RedisClient::bgsave(Self) -> RedisValue 71 | async fn RedisClient::bitcount(Self, String) -> Result[Int, String] 72 | async fn RedisClient::bitcount_range(Self, String, Int, Int) -> Result[Int, String] 73 | async fn RedisClient::bitfield(Self, String, Array[BitFieldOperation]) -> Result[Array[String], String] 74 | async fn RedisClient::bitfield_ro(Self, String, Array[String]) -> Result[Array[String], String] 75 | async fn RedisClient::bitop(Self, String, String, Array[String]) -> Result[Int, String] 76 | async fn RedisClient::bitpos(Self, String, Bit) -> Result[Int, String] 77 | async fn RedisClient::bitpos_range(Self, String, Bit, Int, Int) -> Result[Int, String] 78 | async fn RedisClient::blmove(Self, String, String, wherefrom~ : Int, whereto~ : Int, timeout~ : Int) -> RedisValue 79 | async fn RedisClient::blmpop(Self, timeout~ : Int, String, Array[String], where_~ : String) -> RedisValue 80 | async fn RedisClient::blpop(Self, Array[String], timeout~ : Int) -> RedisValue 81 | async fn RedisClient::brpop(Self, Array[String], timeout~ : Int) -> RedisValue 82 | async fn RedisClient::brpoplpush(Self, String, String, timeout~ : Int) -> RedisValue 83 | async fn RedisClient::bzmpop(Self, String, String, Array[String], String, String?) -> Result[Array[String], String] 84 | async fn RedisClient::bzpopmax(Self, Array[String], String) -> Result[String, String] 85 | async fn RedisClient::bzpopmin(Self, Array[String], String) -> Result[String, String] 86 | async fn RedisClient::client_caching(Self, String) -> Result[String, String] 87 | async fn RedisClient::client_getname(Self) -> Result[String?, String] 88 | async fn RedisClient::client_getredir(Self) -> Result[Int, String] 89 | async fn RedisClient::client_id(Self) -> Result[Int, String] 90 | async fn RedisClient::client_info(Self) -> Result[String, String] 91 | async fn RedisClient::client_kill(Self, String) -> Result[Int, String] 92 | async fn RedisClient::client_list(Self) -> Result[String, String] 93 | async fn RedisClient::client_no_evict(Self, String) -> Result[String, String] 94 | async fn RedisClient::client_no_touch(Self, String) -> Result[String, String] 95 | async fn RedisClient::client_pause(Self, String) -> Result[String, String] 96 | async fn RedisClient::client_reply(Self, String) -> Result[String, String] 97 | async fn RedisClient::client_setname(Self, String) -> Result[String, String] 98 | async fn RedisClient::client_tracking(Self, String) -> Result[String, String] 99 | async fn RedisClient::client_trackinginfo(Self) -> Result[Array[String], String] 100 | async fn RedisClient::client_unblock(Self, String) -> Result[Int, String] 101 | async fn RedisClient::client_unpause(Self) -> Result[String, String] 102 | fn RedisClient::close(Self) -> Unit 103 | async fn RedisClient::cluster_addslots(Self, Array[Int]) -> Result[String, String] 104 | async fn RedisClient::cluster_addslotsrange(Self, Array[(Int, Int)]) -> Result[String, String] 105 | async fn RedisClient::cluster_bumpepoch(Self) -> Result[String, String] 106 | async fn RedisClient::cluster_count_failure_reports(Self, String) -> Result[Int, String] 107 | async fn RedisClient::cluster_countkeysinslot(Self, Int) -> Result[Int, String] 108 | async fn RedisClient::cluster_delslots(Self, Array[Int]) -> Result[String, String] 109 | async fn RedisClient::cluster_delslotsrange(Self, Array[(Int, Int)]) -> Result[String, String] 110 | async fn RedisClient::cluster_failover(Self, option? : String) -> Result[String, String] 111 | async fn RedisClient::cluster_flushslots(Self) -> Result[String, String] 112 | async fn RedisClient::cluster_forget(Self, String) -> Result[String, String] 113 | async fn RedisClient::cluster_getkeysinslot(Self, Int, Int) -> Result[Array[String], String] 114 | async fn RedisClient::cluster_info(Self) -> Result[String, String] 115 | async fn RedisClient::cluster_keyslot(Self, String) -> Result[Int, String] 116 | async fn RedisClient::cluster_links(Self) -> Result[Array[String], String] 117 | async fn RedisClient::cluster_meet(Self, String, Int, cluster_bus_port? : Int) -> Result[String, String] 118 | async fn RedisClient::cluster_myid(Self) -> Result[String, String] 119 | async fn RedisClient::cluster_myshardid(Self) -> Result[String, String] 120 | async fn RedisClient::cluster_nodes(Self) -> Result[String, String] 121 | async fn RedisClient::cluster_replicas(Self, String) -> Result[Array[String], String] 122 | async fn RedisClient::cluster_replicate(Self, String) -> Result[String, String] 123 | async fn RedisClient::cluster_reset(Self, reset_type? : String) -> Result[String, String] 124 | async fn RedisClient::cluster_saveconfig(Self) -> Result[String, String] 125 | async fn RedisClient::cluster_set_config_epoch(Self, Int) -> Result[String, String] 126 | async fn RedisClient::cluster_setslot(Self, Int, String, node_id? : String) -> Result[String, String] 127 | async fn RedisClient::cluster_shards(Self) -> Result[Array[String], String] 128 | async fn RedisClient::cluster_slaves(Self, String) -> Result[Array[String], String] 129 | async fn RedisClient::cluster_slot_stats(Self, Array[Int]) -> Result[Array[String], String] 130 | async fn RedisClient::cluster_slots(Self) -> Result[Array[String], String] 131 | async fn RedisClient::command(Self) -> RedisValue 132 | async fn RedisClient::command_count(Self) -> RedisValue 133 | async fn RedisClient::command_docs(Self, Array[String]) -> RedisValue 134 | async fn RedisClient::command_getkeys(Self, Array[String]) -> RedisValue 135 | async fn RedisClient::command_getkeysandflags(Self, Array[String]) -> RedisValue 136 | async fn RedisClient::command_info(Self, Array[String]) -> RedisValue 137 | async fn RedisClient::command_list(Self) -> RedisValue 138 | async fn RedisClient::config_get(Self, String) -> RedisValue 139 | async fn RedisClient::config_resetstat(Self) -> RedisValue 140 | async fn RedisClient::config_rewrite(Self) -> RedisValue 141 | async fn RedisClient::config_set(Self, String, String) -> RedisValue 142 | async fn RedisClient::copy(Self, String, String) -> Result[Int, String] 143 | async fn RedisClient::dbsize(Self) -> Result[Int, String] 144 | async fn RedisClient::debug_object(Self, String) -> RedisValue 145 | async fn RedisClient::debug_segfault(Self) -> RedisValue 146 | async fn RedisClient::decr(Self, String) -> Result[Int, String] 147 | async fn RedisClient::decrby(Self, String, String) -> Result[Int, String] 148 | async fn RedisClient::del(Self, Array[String]) -> Result[Int, String] 149 | async fn RedisClient::dump(Self, String) -> Result[String?, String] 150 | async fn RedisClient::echo(Self, String) -> Result[String, String] 151 | async fn RedisClient::exists(Self, Array[String]) -> Result[Int, String] 152 | async fn RedisClient::expire(Self, String, String) -> Result[Int, String] 153 | async fn RedisClient::expireat(Self, String, String) -> Result[Int, String] 154 | async fn RedisClient::expiretime(Self, String) -> Result[Int, String] 155 | async fn RedisClient::flushall(Self) -> Result[String, String] 156 | async fn RedisClient::flushdb(Self) -> Result[String, String] 157 | async fn RedisClient::geoadd(Self, String, String, String, String) -> Result[Int, String] 158 | async fn RedisClient::geoadd_multiple(Self, String, Array[GeoCoordinates]) -> Result[Int, String] 159 | async fn RedisClient::geodist(Self, String, String, String) -> Result[String?, String] 160 | async fn RedisClient::geodist_unit(Self, String, String, String, GeoUnits) -> Result[String?, String] 161 | async fn RedisClient::geohash(Self, String, Array[String]) -> Result[Array[String], String] 162 | async fn RedisClient::geopos(Self, String, Array[String]) -> Result[Array[String], String] 163 | async fn RedisClient::georadius(Self, String, String, String, String, GeoUnits) -> Result[Array[String], String] 164 | async fn RedisClient::georadius_ro(Self, String, String, String, String, GeoUnits) -> Result[Array[String], String] 165 | async fn RedisClient::georadiusbymember(Self, String, String, String, GeoUnits) -> Result[Array[String], String] 166 | async fn RedisClient::georadiusbymember_ro(Self, String, String, String, GeoUnits) -> Result[Array[String], String] 167 | async fn RedisClient::geosearch(Self, String, String, String, GeoUnits) -> Result[Array[String], String] 168 | async fn RedisClient::geosearch_box(Self, String, GeoCoordinates, GeoSearchByBox) -> Result[Array[String], String] 169 | async fn RedisClient::geosearch_with_dist(Self, String, String, Double, GeoUnits) -> Result[Array[String], String] 170 | async fn RedisClient::geosearchstore(Self, String, String, String, String, GeoUnits) -> Result[Int, String] 171 | async fn RedisClient::geosearchstore_box(Self, String, String, String, GeoSearchByBox) -> Result[Int, String] 172 | async fn RedisClient::geosearchstore_with_dist(Self, String, String, String, Double, GeoUnits) -> Result[Int, String] 173 | async fn RedisClient::get(Self, String) -> Result[String, String] 174 | async fn RedisClient::getbit(Self, String, Int) -> Result[Int, String] 175 | async fn RedisClient::getdel(Self, String) -> Result[String, String] 176 | async fn RedisClient::getex(Self, String, String) -> Result[String, String] 177 | async fn RedisClient::getrange(Self, String, String, String) -> Result[String, String] 178 | async fn RedisClient::getset(Self, String, String) -> Result[String, String] 179 | async fn RedisClient::hdel(Self, String, Array[String]) -> Result[Int, String] 180 | async fn RedisClient::hello(Self, String) -> Result[Array[String], String] 181 | async fn RedisClient::hexists(Self, String, String) -> Result[Bool, String] 182 | async fn RedisClient::hget(Self, String, String) -> Result[String, String] 183 | async fn RedisClient::hgetall(Self, String) -> Result[Map[String, String], String] 184 | async fn RedisClient::hkeys(Self, String) -> Result[Array[String], String] 185 | async fn RedisClient::hlen(Self, String) -> Result[Int, String] 186 | async fn RedisClient::hset(Self, String, String, String) -> Result[Int, String] 187 | async fn RedisClient::hvals(Self, String) -> Result[Array[String], String] 188 | async fn RedisClient::incr(Self, String) -> Result[Int, String] 189 | async fn RedisClient::incrby(Self, String, String) -> Result[Int, String] 190 | async fn RedisClient::incrbyfloat(Self, String, String) -> Result[String, String] 191 | async fn RedisClient::info(Self) -> RedisValue 192 | async fn RedisClient::info_section(Self, String) -> RedisValue 193 | async fn RedisClient::json_arrappend(Self, String, String, Array[String]) -> Result[Int, String] 194 | async fn RedisClient::json_arrinsert(Self, String, String, Int, Array[String]) -> Result[Int, String] 195 | async fn RedisClient::json_arrlen(Self, String, String) -> Result[Int, String] 196 | async fn RedisClient::json_arrpop(Self, String, String, Int) -> Result[String, String] 197 | async fn RedisClient::json_arrtrim(Self, String, String, Int, Int) -> Result[Int, String] 198 | async fn RedisClient::json_clear(Self, String, String) -> Result[Int, String] 199 | async fn RedisClient::json_debug(Self, String, String, String) -> Result[String, String] 200 | async fn RedisClient::json_del(Self, String, String) -> Result[Int, String] 201 | async fn RedisClient::json_forget(Self, String, String) -> Result[Int, String] 202 | async fn RedisClient::json_get(Self, String, String) -> Result[String, String] 203 | async fn RedisClient::json_numincrby(Self, String, String, String) -> Result[String, String] 204 | async fn RedisClient::json_nummultby(Self, String, String, String) -> Result[String, String] 205 | async fn RedisClient::json_objkeys(Self, String, String) -> Result[Array[String], String] 206 | async fn RedisClient::json_objlen(Self, String, String) -> Result[Int, String] 207 | async fn RedisClient::json_resp(Self, String, String) -> Result[Array[String], String] 208 | async fn RedisClient::json_set(Self, String, String, String) -> Result[String, String] 209 | async fn RedisClient::json_strappend(Self, String, String, String) -> Result[Int, String] 210 | async fn RedisClient::json_strlen(Self, String, String) -> Result[Int, String] 211 | async fn RedisClient::json_toggle(Self, String, String) -> Result[Int, String] 212 | async fn RedisClient::json_type(Self, String, String) -> Result[String, String] 213 | async fn RedisClient::keys(Self, String) -> Result[Array[String], String] 214 | async fn RedisClient::lastsave(Self) -> RedisValue 215 | async fn RedisClient::latency_doctor(Self) -> RedisValue 216 | async fn RedisClient::latency_graph(Self, String) -> RedisValue 217 | async fn RedisClient::latency_history(Self, String) -> RedisValue 218 | async fn RedisClient::latency_latest(Self) -> RedisValue 219 | async fn RedisClient::latency_reset(Self, Array[String]) -> RedisValue 220 | async fn RedisClient::lindex(Self, String, index~ : Int) -> Result[String, String] 221 | async fn RedisClient::linsert(Self, String, where_~ : String, pivot~ : String, element~ : String) -> Result[Int, String] 222 | async fn RedisClient::llen(Self, String) -> Result[Int, String] 223 | async fn RedisClient::lmove(Self, String, String, wherefrom~ : Int, whereto~ : Int) -> RedisValue 224 | async fn RedisClient::lmpop(Self, String, Array[String], where_~ : String) -> RedisValue 225 | async fn RedisClient::lpop(Self, String) -> Result[String, String] 226 | async fn RedisClient::lpos(Self, String, element~ : String) -> RedisValue 227 | async fn RedisClient::lpush(Self, String, Array[String]) -> Result[Int, String] 228 | async fn RedisClient::lpushx(Self, String, Array[String]) -> Result[Int, String] 229 | async fn RedisClient::lrange(Self, String, start~ : Int, stop~ : Int) -> Result[Array[String], String] 230 | async fn RedisClient::lrem(Self, String, count~ : Int, value~ : String) -> Result[Int, String] 231 | async fn RedisClient::lset(Self, String, index~ : Int, value~ : String) -> Result[String, String] 232 | async fn RedisClient::ltrim(Self, String, start~ : Int, stop~ : Int) -> Result[String, String] 233 | async fn RedisClient::memory_doctor(Self) -> RedisValue 234 | async fn RedisClient::memory_malloc_stats(Self) -> RedisValue 235 | async fn RedisClient::memory_purge(Self) -> RedisValue 236 | async fn RedisClient::memory_stats(Self) -> RedisValue 237 | async fn RedisClient::memory_usage(Self, String) -> RedisValue 238 | async fn RedisClient::mget(Self, Array[String]) -> Result[Array[String], String] 239 | async fn RedisClient::migrate(Self, String, String, String, String, String) -> Result[String, String] 240 | async fn RedisClient::module_list(Self) -> RedisValue 241 | async fn RedisClient::module_load(Self, String) -> RedisValue 242 | async fn RedisClient::module_loadex(Self, String, Array[String]) -> RedisValue 243 | async fn RedisClient::module_unload(Self, String) -> RedisValue 244 | async fn RedisClient::monitor(Self) -> RedisValue 245 | async fn RedisClient::move_(Self, String, String) -> Result[Int, String] 246 | async fn RedisClient::mset(Self, Map[String, String]) -> Result[String, String] 247 | async fn RedisClient::msetnx(Self, Map[String, String]) -> Result[Int, String] 248 | async fn RedisClient::object_encoding(Self, String) -> Result[String?, String] 249 | async fn RedisClient::object_freq(Self, String) -> Result[Int, String] 250 | async fn RedisClient::object_idletime(Self, String) -> Result[Int, String] 251 | async fn RedisClient::object_refcount(Self, String) -> Result[Int, String] 252 | async fn RedisClient::persist(Self, String) -> Result[Int, String] 253 | async fn RedisClient::pexpire(Self, String, String) -> Result[Int, String] 254 | async fn RedisClient::pexpireat(Self, String, String) -> Result[Int, String] 255 | async fn RedisClient::pexpiretime(Self, String) -> Result[Int, String] 256 | async fn RedisClient::ping(Self) -> Result[String, String] 257 | async fn RedisClient::psetex(Self, String, String, String) -> Result[String, String] 258 | async fn RedisClient::psubscribe(Self, Array[String]) -> Result[Array[String], String] 259 | async fn RedisClient::psync(Self, String, String) -> RedisValue 260 | async fn RedisClient::pttl(Self, String) -> Result[Int, String] 261 | async fn RedisClient::publish(Self, String, String) -> Result[Int, String] 262 | async fn RedisClient::pubsub_channels(Self, pattern? : String) -> Result[Array[String], String] 263 | async fn RedisClient::pubsub_numpat(Self) -> Result[Int, String] 264 | async fn RedisClient::pubsub_numsub(Self, channels? : Array[String]) -> Result[Array[String], String] 265 | async fn RedisClient::pubsub_shardchannels(Self, pattern? : String) -> Result[Array[String], String] 266 | async fn RedisClient::pubsub_shardnumsub(Self, channels? : Array[String]) -> Result[Array[String], String] 267 | async fn RedisClient::punsubscribe(Self, patterns? : Array[String]) -> Result[Array[String], String] 268 | async fn RedisClient::quit(Self) -> Result[String, String] 269 | async fn RedisClient::randomkey(Self) -> Result[String?, String] 270 | async fn RedisClient::read(Self, FixedArray[Byte]) -> Int 271 | async fn RedisClient::read_redis_value(Self) -> RedisValue 272 | async fn RedisClient::read_response(Self) -> String 273 | async fn RedisClient::rename(Self, String, String) -> Result[String, String] 274 | async fn RedisClient::renamenx(Self, String, String) -> Result[Int, String] 275 | async fn RedisClient::replconf(Self, String, String) -> RedisValue 276 | async fn RedisClient::replicaof(Self, String, String) -> RedisValue 277 | async fn RedisClient::reset(Self) -> Result[String, String] 278 | async fn RedisClient::restore(Self, String, String, String) -> Result[String, String] 279 | async fn RedisClient::role(Self) -> RedisValue 280 | async fn RedisClient::rpop(Self, String) -> Result[String, String] 281 | async fn RedisClient::rpoplpush(Self, String, String) -> RedisValue 282 | async fn RedisClient::rpush(Self, String, Array[String]) -> Result[Int, String] 283 | async fn RedisClient::rpushx(Self, String, Array[String]) -> Result[Int, String] 284 | async fn RedisClient::sadd(Self, String, Array[String]) -> Result[Int, String] 285 | async fn RedisClient::save(Self) -> RedisValue 286 | async fn RedisClient::scan(Self, String) -> Result[Array[String], String] 287 | async fn RedisClient::scard(Self, String) -> Result[Int, String] 288 | async fn RedisClient::sdiff(Self, Array[String]) -> Result[Array[String], String] 289 | async fn RedisClient::sdiffstore(Self, String, Array[String]) -> Result[Int, String] 290 | async fn RedisClient::select(Self, String) -> Result[String, String] 291 | async fn RedisClient::send(Self, Array[String]) -> RedisValue 292 | async fn RedisClient::set(Self, String, String, condition? : SetCondition, get? : Bool, expiration? : SetExpiration) -> Result[String, String] 293 | async fn RedisClient::setbit(Self, String, Int, Bit) -> Result[Int, String] 294 | async fn RedisClient::setex(Self, String, String, String) -> Result[String, String] 295 | async fn RedisClient::setnx(Self, String, String) -> Result[Int, String] 296 | async fn RedisClient::setrange(Self, String, String, String) -> Result[Int, String] 297 | async fn RedisClient::shutdown(Self) -> RedisValue 298 | async fn RedisClient::sinter(Self, Array[String]) -> Result[Int, String] 299 | async fn RedisClient::sintercard(Self, String, Array[String]) -> Result[Int, String] 300 | async fn RedisClient::sinterstore(Self, String, Array[String]) -> Result[Int, String] 301 | async fn RedisClient::sismember(Self, String, String) -> Result[Int, String] 302 | async fn RedisClient::slaveof(Self, String, String) -> RedisValue 303 | async fn RedisClient::slowlog_get(Self) -> RedisValue 304 | async fn RedisClient::slowlog_len(Self) -> RedisValue 305 | async fn RedisClient::slowlog_reset(Self) -> RedisValue 306 | async fn RedisClient::smembers(Self, String) -> Result[Array[String], String] 307 | async fn RedisClient::smismember(Self, String, Array[String]) -> Result[Array[String], String] 308 | async fn RedisClient::smove(Self, String, String, String) -> Result[Int, String] 309 | async fn RedisClient::sort(Self, String) -> Result[Array[String], String] 310 | async fn RedisClient::sort_ro(Self, String) -> Result[Array[String], String] 311 | async fn RedisClient::spop(Self, String) -> Result[String, String] 312 | async fn RedisClient::spublish(Self, String, String) -> Result[Int, String] 313 | async fn RedisClient::srandmember(Self, String) -> Result[String, String] 314 | async fn RedisClient::srem(Self, String, Array[String]) -> Result[Int, String] 315 | async fn RedisClient::sscan(Self, String, Int, match_pattern? : String, count? : Int) -> Result[Array[String], String] 316 | async fn RedisClient::ssubscribe(Self, Array[String]) -> Result[Array[String], String] 317 | async fn RedisClient::strlen(Self, String) -> Result[Int, String] 318 | async fn RedisClient::subscribe(Self, Array[String]) -> Result[Array[String], String] 319 | async fn RedisClient::substr(Self, String, Int, Int) -> Result[String, String] 320 | async fn RedisClient::sunion(Self, Array[String]) -> Result[Int, String] 321 | async fn RedisClient::sunionstore(Self, String, Array[String]) -> Result[Int, String] 322 | async fn RedisClient::sunsubscribe(Self, channels? : Array[String]) -> Result[Array[String], String] 323 | async fn RedisClient::swapdb(Self, String, String) -> Result[String, String] 324 | async fn RedisClient::sync(Self) -> RedisValue 325 | async fn RedisClient::time(Self) -> RedisValue 326 | async fn RedisClient::touch(Self, Array[String]) -> Result[Int, String] 327 | async fn RedisClient::ttl(Self, String) -> Result[Int, String] 328 | async fn RedisClient::type_(Self, String) -> Result[String, String] 329 | async fn RedisClient::unlink(Self, Array[String]) -> Result[Int, String] 330 | async fn RedisClient::unsubscribe(Self, channels? : Array[String]) -> Result[Array[String], String] 331 | async fn RedisClient::wait(Self, String, String) -> Result[Int, String] 332 | async fn RedisClient::waitaof(Self, String, String, String) -> Result[Int, String] 333 | async fn RedisClient::write(Self, String) -> Unit 334 | async fn RedisClient::xack(Self, String, String, Array[String]) -> Result[Int, String] 335 | async fn RedisClient::xadd(Self, String, String, Map[String, String]) -> Result[String, String] 336 | async fn RedisClient::xautoclaim(Self, String, String, String, Int, start~ : String, count? : Int) -> Result[Array[String], String] 337 | async fn RedisClient::xclaim(Self, String, String, String, Int, Array[String]) -> Result[Array[String], String] 338 | async fn RedisClient::xdel(Self, String, Array[String]) -> Result[Int, String] 339 | async fn RedisClient::xgroup_create(Self, String, String, String, Bool) -> Result[String, String] 340 | async fn RedisClient::xgroup_createconsumer(Self, String, String, String) -> Result[Int, String] 341 | async fn RedisClient::xgroup_delconsumer(Self, String, String, String) -> Result[Int, String] 342 | async fn RedisClient::xgroup_destroy(Self, String, String) -> Result[Int, String] 343 | async fn RedisClient::xgroup_setid(Self, String, String, String) -> Result[String, String] 344 | async fn RedisClient::xinfo_consumers(Self, String, String) -> Result[Array[String], String] 345 | async fn RedisClient::xinfo_groups(Self, String) -> Result[Array[String], String] 346 | async fn RedisClient::xinfo_stream(Self, String) -> Result[Array[String], String] 347 | async fn RedisClient::xlen(Self, String) -> Result[Int, String] 348 | async fn RedisClient::xpending(Self, String, String) -> Result[Array[String], String] 349 | async fn RedisClient::xpending_range(Self, String, String, start~ : String, end~ : String, count~ : Int, consumer? : String) -> Result[Array[String], String] 350 | async fn RedisClient::xrange(Self, String, start~ : String, end~ : String, count? : Int) -> Result[Array[RedisStreamMessage], String] 351 | async fn RedisClient::xread(Self, count? : Int, block? : Int, Array[(String, String)]) -> Result[RedisStream, String] 352 | async fn RedisClient::xreadgroup(Self, String, String, count? : Int, block? : Int, noack? : Bool, Array[(String, String)]) -> Result[RedisStream, String] 353 | async fn RedisClient::xrevrange(Self, String, end~ : String, start~ : String, count? : Int) -> Result[Array[RedisStreamMessage], String] 354 | async fn RedisClient::xtrim(Self, String, strategy~ : XTrimStrategy, threshold~ : Int64, approximate? : Bool, limit? : Int, keepref? : Bool, delref? : Bool, acked? : Bool) -> Result[Int, String] 355 | async fn RedisClient::zadd(Self, String, Array[ZAddMember]) -> Result[Int, String] 356 | async fn RedisClient::zcard(Self, String) -> Result[Int, String] 357 | async fn RedisClient::zcount(Self, String, min~ : String, max~ : String) -> Result[Int, String] 358 | async fn RedisClient::zdiff(Self, String, Array[String]) -> Result[Array[String], String] 359 | async fn RedisClient::zdiffstore(Self, String, String, Array[String]) -> Result[Int, String] 360 | async fn RedisClient::zincrby(Self, String, increment~ : String, value~ : String) -> Result[String, String] 361 | async fn RedisClient::zinter(Self, String, Array[String]) -> Result[Array[String], String] 362 | async fn RedisClient::zintercard(Self, String, Array[String]) -> Result[Int, String] 363 | async fn RedisClient::zinterstore(Self, String, String, Array[String]) -> Result[Int, String] 364 | async fn RedisClient::zlexcount(Self, String, min~ : String, max~ : String) -> Result[Int, String] 365 | async fn RedisClient::zmpop(Self, String, Array[String], String) -> Result[Array[String], String] 366 | async fn RedisClient::zmscore(Self, String, Array[String]) -> Result[Array[String], String] 367 | async fn RedisClient::zpopmax(Self, String) -> Result[String, String] 368 | async fn RedisClient::zpopmin(Self, String) -> Result[String, String] 369 | async fn RedisClient::zrandmember(Self, String) -> Result[String, String] 370 | async fn RedisClient::zrange(Self, String, start~ : String, stop~ : String) -> Result[Array[String], String] 371 | async fn RedisClient::zrangebylex(Self, String, min~ : String, max~ : String) -> Result[Array[String], String] 372 | async fn RedisClient::zrangebyscore(Self, String, min~ : String, max~ : String) -> Result[Array[String], String] 373 | async fn RedisClient::zrangestore(Self, String, String, min~ : String, max~ : String) -> Result[Int, String] 374 | async fn RedisClient::zrank(Self, String, String) -> Result[Int, String] 375 | async fn RedisClient::zrem(Self, String, Array[String]) -> Result[Int, String] 376 | async fn RedisClient::zremrangebylex(Self, String, String, String) -> Result[Int, String] 377 | async fn RedisClient::zremrangebyrank(Self, String, String, String) -> Result[Int, String] 378 | async fn RedisClient::zremrangebyscore(Self, String, String, String) -> Result[Int, String] 379 | async fn RedisClient::zrevrange(Self, String, start~ : String, stop~ : String) -> Result[Array[String], String] 380 | async fn RedisClient::zrevrangebylex(Self, String, max~ : String, min~ : String) -> Result[Array[String], String] 381 | async fn RedisClient::zrevrangebyscore(Self, String, max~ : String, min~ : String) -> Result[Array[String], String] 382 | async fn RedisClient::zrevrank(Self, String, String) -> Result[Int, String] 383 | async fn RedisClient::zscan(Self, String, String) -> Result[Array[String], String] 384 | async fn RedisClient::zscore(Self, String, String) -> Result[String, String] 385 | async fn RedisClient::zunion(Self, String, Array[String]) -> Result[Array[String], String] 386 | async fn RedisClient::zunionstore(Self, String, String, Array[String]) -> Result[Int, String] 387 | 388 | pub(all) struct RedisStream { 389 | name : String 390 | messages : Array[RedisStreamMessage] 391 | } 392 | impl Show for RedisStream 393 | 394 | pub(all) struct RedisStreamMessage { 395 | id : String 396 | fields : Array[String] 397 | } 398 | impl Show for RedisStreamMessage 399 | 400 | pub(all) enum RedisValue { 401 | SimpleString(String) 402 | Error(String) 403 | Integer(Int) 404 | BulkString(String?) 405 | Array(Array[RedisValue]) 406 | } 407 | fn RedisValue::to_array(Self) -> Result[Array[String], String] 408 | fn RedisValue::to_bool(Self) -> Result[Bool, String] 409 | fn RedisValue::to_bulk_string(Self) -> Result[String?, String] 410 | fn RedisValue::to_int(Self) -> Result[Int, String] 411 | fn RedisValue::to_stream(Self) -> Result[RedisStream, String] 412 | fn RedisValue::to_stream_message_array(Self) -> Result[Array[RedisStreamMessage], String] 413 | fn RedisValue::to_string(Self) -> Result[String, String] 414 | impl Eq for RedisValue 415 | impl Hash for RedisValue 416 | impl Show for RedisValue 417 | 418 | pub(all) enum SetCondition { 419 | NX 420 | XX 421 | IFEQ(String) 422 | } 423 | 424 | pub(all) enum SetExpiration { 425 | EX(Int) 426 | PX(Int) 427 | EXAT(Int) 428 | PXAT(Int) 429 | KEEPTTL 430 | } 431 | 432 | pub(all) enum XTrimStrategy { 433 | MaxLen 434 | MinID 435 | } 436 | 437 | pub(all) struct ZAddMember { 438 | score : String 439 | value : String 440 | } 441 | 442 | // Type aliases 443 | pub typealias Result[Array[String], String] as RedisArray 444 | 445 | pub typealias Result[Bool, String] as RedisBool 446 | 447 | pub typealias Result[String?, String] as RedisBulkString 448 | 449 | pub typealias Result[Map[String, String], String] as RedisHash 450 | 451 | pub typealias Result[Int, String] as RedisInt 452 | 453 | pub typealias Result[T, String] as RedisResult[T] 454 | 455 | pub typealias Result[Array[RedisStreamMessage], String] as RedisStreamMessageArray 456 | 457 | pub typealias Result[RedisStream, String] as RedisStreamResult 458 | 459 | pub typealias Result[String, String] as RedisString 460 | 461 | // Traits 462 | 463 | --------------------------------------------------------------------------------