this repo has no description
at main 286 lines 12 kB view raw
1//! Redis string commands 2//! 3//! Strings are the most basic Redis value type. Despite the name, they can hold 4//! any binary data up to 512MB. Common uses: 5//! - Caching (SET/GET with TTL) 6//! - Counters (INCR/DECR) 7//! - Bit operations (SETBIT/GETBIT) 8//! - Simple key-value storage 9//! 10//! ## Examples 11//! 12//! ```zig 13//! try client.set("name", "alice"); 14//! const name = try client.get("name"); // "alice" 15//! 16//! try client.setEx("session", "data", 3600); // expires in 1 hour 17//! 18//! try client.set("counter", "0"); 19//! const n = try client.incr("counter"); // 1 20//! const m = try client.incrBy("counter", 10); // 11 21//! ``` 22 23const std = @import("std"); 24const Client = @import("../client.zig").Client; 25const Value = @import("../resp.zig").Value; 26const CommandError = @import("../resp.zig").CommandError; 27const ClientError = @import("../resp.zig").ClientError; 28 29/// String command implementations. 30/// These are implemented as methods that take a Client pointer. 31pub const StringCommands = struct { 32 client: *Client, 33 34 pub fn init(client: *Client) StringCommands { 35 return .{ .client = client }; 36 } 37 38 // ======================================================================== 39 // Basic Operations 40 // ======================================================================== 41 42 /// GET key - retrieve string value 43 /// Returns null if key doesn't exist. 44 pub fn get(self: *StringCommands, key: []const u8) ClientError!?[]const u8 { 45 const result = try self.client.sendCommand(&.{ "GET", key }); 46 return switch (result) { 47 .bulk => |b| b, 48 .nil => null, 49 .err => return CommandError.RedisError, 50 else => null, 51 }; 52 } 53 54 /// SET key value - store string value 55 pub fn set(self: *StringCommands, key: []const u8, value: []const u8) ClientError!void { 56 const result = try self.client.sendCommand(&.{ "SET", key, value }); 57 if (result.isError()) return CommandError.RedisError; 58 } 59 60 /// SET key value NX - set only if key doesn't exist 61 /// Returns true if set, false if key existed. 62 pub fn setNx(self: *StringCommands, key: []const u8, value: []const u8) ClientError!bool { 63 const result = try self.client.sendCommand(&.{ "SET", key, value, "NX" }); 64 return switch (result) { 65 .string => true, 66 .nil => false, 67 .err => return CommandError.RedisError, 68 else => false, 69 }; 70 } 71 72 /// SET key value XX - set only if key exists 73 /// Returns true if set, false if key didn't exist. 74 pub fn setXx(self: *StringCommands, key: []const u8, value: []const u8) ClientError!bool { 75 const result = try self.client.sendCommand(&.{ "SET", key, value, "XX" }); 76 return switch (result) { 77 .string => true, 78 .nil => false, 79 .err => return CommandError.RedisError, 80 else => false, 81 }; 82 } 83 84 /// SET key value EX seconds - set with TTL in seconds 85 pub fn setEx(self: *StringCommands, key: []const u8, value: []const u8, seconds: u32) ClientError!void { 86 var buf: [16]u8 = undefined; 87 const sec_str = std.fmt.bufPrint(&buf, "{d}", .{seconds}) catch unreachable; 88 const result = try self.client.sendCommand(&.{ "SET", key, value, "EX", sec_str }); 89 if (result.isError()) return CommandError.RedisError; 90 } 91 92 /// SET key value PX milliseconds - set with TTL in milliseconds 93 pub fn setPx(self: *StringCommands, key: []const u8, value: []const u8, millis: u64) ClientError!void { 94 var buf: [24]u8 = undefined; 95 const ms_str = std.fmt.bufPrint(&buf, "{d}", .{millis}) catch unreachable; 96 const result = try self.client.sendCommand(&.{ "SET", key, value, "PX", ms_str }); 97 if (result.isError()) return CommandError.RedisError; 98 } 99 100 /// GETSET key value - set new value and return old value (deprecated, use GETEX) 101 pub fn getSet(self: *StringCommands, key: []const u8, value: []const u8) ClientError!?[]const u8 { 102 const result = try self.client.sendCommand(&.{ "GETSET", key, value }); 103 return switch (result) { 104 .bulk => |b| b, 105 .nil => null, 106 .err => return CommandError.RedisError, 107 else => null, 108 }; 109 } 110 111 /// MGET key [key ...] - get multiple values 112 /// Returns array of values (null for missing keys). 113 pub fn mget(self: *StringCommands, keys: []const []const u8) ClientError![]const Value { 114 var args = try self.client.allocator.alloc([]const u8, keys.len + 1); 115 defer self.client.allocator.free(args); 116 args[0] = "MGET"; 117 @memcpy(args[1..], keys); 118 119 const result = try self.client.sendCommand(args); 120 return switch (result) { 121 .array => |a| a, 122 else => &.{}, 123 }; 124 } 125 126 /// MSET key value [key value ...] - set multiple key-value pairs atomically 127 pub fn mset(self: *StringCommands, pairs: []const [2][]const u8) ClientError!void { 128 const arg_count = 1 + pairs.len * 2; 129 var args = try self.client.allocator.alloc([]const u8, arg_count); 130 defer self.client.allocator.free(args); 131 132 args[0] = "MSET"; 133 for (pairs, 0..) |pair, i| { 134 args[1 + i * 2] = pair[0]; 135 args[1 + i * 2 + 1] = pair[1]; 136 } 137 138 const result = try self.client.sendCommand(args); 139 if (result.isError()) return CommandError.RedisError; 140 } 141 142 /// MSETNX key value [key value ...] - set multiple only if none exist 143 /// Returns true if all were set, false if any existed. 144 pub fn msetNx(self: *StringCommands, pairs: []const [2][]const u8) ClientError!bool { 145 const arg_count = 1 + pairs.len * 2; 146 var args = try self.client.allocator.alloc([]const u8, arg_count); 147 defer self.client.allocator.free(args); 148 149 args[0] = "MSETNX"; 150 for (pairs, 0..) |pair, i| { 151 args[1 + i * 2] = pair[0]; 152 args[1 + i * 2 + 1] = pair[1]; 153 } 154 155 const result = try self.client.sendCommand(args); 156 return switch (result) { 157 .integer => |i| i == 1, 158 else => false, 159 }; 160 } 161 162 // ======================================================================== 163 // Numeric Operations 164 // ======================================================================== 165 166 /// INCR key - increment integer value by 1 167 pub fn incr(self: *StringCommands, key: []const u8) ClientError!i64 { 168 const result = try self.client.sendCommand(&.{ "INCR", key }); 169 return switch (result) { 170 .integer => |i| i, 171 .err => return CommandError.WrongType, 172 else => return CommandError.RedisError, 173 }; 174 } 175 176 /// INCRBY key increment - increment by specific amount 177 pub fn incrBy(self: *StringCommands, key: []const u8, increment: i64) ClientError!i64 { 178 var buf: [24]u8 = undefined; 179 const inc_str = std.fmt.bufPrint(&buf, "{d}", .{increment}) catch unreachable; 180 const result = try self.client.sendCommand(&.{ "INCRBY", key, inc_str }); 181 return switch (result) { 182 .integer => |i| i, 183 .err => return CommandError.WrongType, 184 else => return CommandError.RedisError, 185 }; 186 } 187 188 /// INCRBYFLOAT key increment - increment by float 189 pub fn incrByFloat(self: *StringCommands, key: []const u8, increment: f64) ClientError!f64 { 190 var buf: [32]u8 = undefined; 191 const inc_str = std.fmt.bufPrint(&buf, "{d}", .{increment}) catch unreachable; 192 const result = try self.client.sendCommand(&.{ "INCRBYFLOAT", key, inc_str }); 193 return switch (result) { 194 .bulk => |b| if (b) |s| std.fmt.parseFloat(f64, s) catch return CommandError.RedisError else return CommandError.RedisError, 195 .err => return CommandError.WrongType, 196 else => return CommandError.RedisError, 197 }; 198 } 199 200 /// DECR key - decrement integer value by 1 201 pub fn decr(self: *StringCommands, key: []const u8) ClientError!i64 { 202 const result = try self.client.sendCommand(&.{ "DECR", key }); 203 return switch (result) { 204 .integer => |i| i, 205 .err => return CommandError.WrongType, 206 else => return CommandError.RedisError, 207 }; 208 } 209 210 /// DECRBY key decrement - decrement by specific amount 211 pub fn decrBy(self: *StringCommands, key: []const u8, decrement: i64) ClientError!i64 { 212 var buf: [24]u8 = undefined; 213 const dec_str = std.fmt.bufPrint(&buf, "{d}", .{decrement}) catch unreachable; 214 const result = try self.client.sendCommand(&.{ "DECRBY", key, dec_str }); 215 return switch (result) { 216 .integer => |i| i, 217 .err => return CommandError.WrongType, 218 else => return CommandError.RedisError, 219 }; 220 } 221 222 // ======================================================================== 223 // String Manipulation 224 // ======================================================================== 225 226 /// APPEND key value - append to string 227 /// Returns new length of string. 228 pub fn append(self: *StringCommands, key: []const u8, value: []const u8) ClientError!i64 { 229 const result = try self.client.sendCommand(&.{ "APPEND", key, value }); 230 return switch (result) { 231 .integer => |i| i, 232 .err => return CommandError.WrongType, 233 else => 0, 234 }; 235 } 236 237 /// STRLEN key - get string length 238 pub fn strlen(self: *StringCommands, key: []const u8) ClientError!i64 { 239 const result = try self.client.sendCommand(&.{ "STRLEN", key }); 240 return switch (result) { 241 .integer => |i| i, 242 else => 0, 243 }; 244 } 245 246 /// GETRANGE key start end - get substring 247 pub fn getRange(self: *StringCommands, key: []const u8, start: i64, end: i64) ClientError!?[]const u8 { 248 var start_buf: [24]u8 = undefined; 249 var end_buf: [24]u8 = undefined; 250 const start_str = std.fmt.bufPrint(&start_buf, "{d}", .{start}) catch unreachable; 251 const end_str = std.fmt.bufPrint(&end_buf, "{d}", .{end}) catch unreachable; 252 253 const result = try self.client.sendCommand(&.{ "GETRANGE", key, start_str, end_str }); 254 return switch (result) { 255 .bulk => |b| b, 256 else => null, 257 }; 258 } 259 260 /// SETRANGE key offset value - overwrite part of string 261 /// Returns new length of string. 262 pub fn setRange(self: *StringCommands, key: []const u8, offset: usize, value: []const u8) ClientError!i64 { 263 var buf: [24]u8 = undefined; 264 const offset_str = std.fmt.bufPrint(&buf, "{d}", .{offset}) catch unreachable; 265 const result = try self.client.sendCommand(&.{ "SETRANGE", key, offset_str, value }); 266 return switch (result) { 267 .integer => |i| i, 268 .err => return CommandError.WrongType, 269 else => 0, 270 }; 271 } 272}; 273 274// ============================================================================ 275// Client Extension Methods 276// ============================================================================ 277 278/// Extend Client with string commands. 279/// Usage: `try client.strings().get("key")` 280pub fn strings(client: *Client) StringCommands { 281 return StringCommands.init(client); 282} 283 284// Direct convenience methods on Client would require modifying client.zig 285// For now, users can call: client.strings().get("key") 286// Or we can add these as pub fn in the main redis.zig module