this repo has no description
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