this repo has no description
1//! Redis generic key commands
2//!
3//! These commands work on keys regardless of their value type.
4//! Common uses:
5//! - Key existence checks (EXISTS)
6//! - Key deletion (DEL, UNLINK)
7//! - TTL management (EXPIRE, TTL, PERSIST)
8//! - Key scanning (SCAN, KEYS)
9//! - Key type inspection (TYPE)
10//!
11//! ## Examples
12//!
13//! ```zig
14//! const exists = try client.keys().exists(&.{"key1", "key2"}); // count of existing
15//! const deleted = try client.keys().del(&.{"key1", "key2"}); // count deleted
16//!
17//! _ = try client.keys().expire("session", 3600);
18//! const ttl = try client.keys().ttl("session"); // seconds remaining
19//! ```
20
21const std = @import("std");
22const Client = @import("../client.zig").Client;
23const Value = @import("../resp.zig").Value;
24const CommandError = @import("../resp.zig").CommandError;
25const ClientError = @import("../resp.zig").ClientError;
26
27/// Generic key command implementations.
28pub const KeyCommands = struct {
29 client: *Client,
30
31 pub fn init(client: *Client) KeyCommands {
32 return .{ .client = client };
33 }
34
35 // ========================================================================
36 // Key Existence and Deletion
37 // ========================================================================
38
39 /// DEL key [key ...] - delete keys
40 /// Returns count of keys deleted.
41 pub fn del(self: *KeyCommands, key_list: []const []const u8) ClientError!i64 {
42 var args = try self.client.allocator.alloc([]const u8, key_list.len + 1);
43 defer self.client.allocator.free(args);
44 args[0] = "DEL";
45 @memcpy(args[1..], key_list);
46
47 const result = try self.client.sendCommand(args);
48 return switch (result) {
49 .integer => |i| i,
50 else => 0,
51 };
52 }
53
54 /// UNLINK key [key ...] - delete keys asynchronously
55 /// Like DEL but non-blocking. Returns count of keys unlinked.
56 pub fn unlink(self: *KeyCommands, key_list: []const []const u8) ClientError!i64 {
57 var args = try self.client.allocator.alloc([]const u8, key_list.len + 1);
58 defer self.client.allocator.free(args);
59 args[0] = "UNLINK";
60 @memcpy(args[1..], key_list);
61
62 const result = try self.client.sendCommand(args);
63 return switch (result) {
64 .integer => |i| i,
65 else => 0,
66 };
67 }
68
69 /// EXISTS key [key ...] - check if keys exist
70 /// Returns count of keys that exist.
71 pub fn exists(self: *KeyCommands, key_list: []const []const u8) ClientError!i64 {
72 var args = try self.client.allocator.alloc([]const u8, key_list.len + 1);
73 defer self.client.allocator.free(args);
74 args[0] = "EXISTS";
75 @memcpy(args[1..], key_list);
76
77 const result = try self.client.sendCommand(args);
78 return switch (result) {
79 .integer => |i| i,
80 else => 0,
81 };
82 }
83
84 /// TYPE key - get key's value type
85 /// Returns "string", "list", "set", "zset", "hash", "stream", or "none".
86 pub fn keyType(self: *KeyCommands, key: []const u8) ClientError![]const u8 {
87 const result = try self.client.sendCommand(&.{ "TYPE", key });
88 return switch (result) {
89 .string => |s| s,
90 else => "none",
91 };
92 }
93
94 // ========================================================================
95 // TTL Management
96 // ========================================================================
97
98 /// EXPIRE key seconds - set TTL in seconds
99 /// Returns true if timeout was set.
100 pub fn expire(self: *KeyCommands, key: []const u8, seconds: u32) ClientError!bool {
101 var buf: [16]u8 = undefined;
102 const sec_str = std.fmt.bufPrint(&buf, "{d}", .{seconds}) catch unreachable;
103 const result = try self.client.sendCommand(&.{ "EXPIRE", key, sec_str });
104 return switch (result) {
105 .integer => |i| i == 1,
106 else => false,
107 };
108 }
109
110 /// EXPIREAT key unix-timestamp - set expiration as Unix timestamp
111 pub fn expireAt(self: *KeyCommands, key: []const u8, timestamp: i64) ClientError!bool {
112 var buf: [24]u8 = undefined;
113 const ts_str = std.fmt.bufPrint(&buf, "{d}", .{timestamp}) catch unreachable;
114 const result = try self.client.sendCommand(&.{ "EXPIREAT", key, ts_str });
115 return switch (result) {
116 .integer => |i| i == 1,
117 else => false,
118 };
119 }
120
121 /// PEXPIRE key milliseconds - set TTL in milliseconds
122 pub fn pexpire(self: *KeyCommands, key: []const u8, millis: u64) ClientError!bool {
123 var buf: [24]u8 = undefined;
124 const ms_str = std.fmt.bufPrint(&buf, "{d}", .{millis}) catch unreachable;
125 const result = try self.client.sendCommand(&.{ "PEXPIRE", key, ms_str });
126 return switch (result) {
127 .integer => |i| i == 1,
128 else => false,
129 };
130 }
131
132 /// TTL key - get remaining TTL in seconds
133 /// Returns null if key doesn't exist or has no TTL.
134 pub fn ttl(self: *KeyCommands, key: []const u8) ClientError!?i64 {
135 const result = try self.client.sendCommand(&.{ "TTL", key });
136 return switch (result) {
137 .integer => |i| if (i < 0) null else i,
138 else => null,
139 };
140 }
141
142 /// PTTL key - get remaining TTL in milliseconds
143 pub fn pttl(self: *KeyCommands, key: []const u8) ClientError!?i64 {
144 const result = try self.client.sendCommand(&.{ "PTTL", key });
145 return switch (result) {
146 .integer => |i| if (i < 0) null else i,
147 else => null,
148 };
149 }
150
151 /// PERSIST key - remove TTL
152 /// Returns true if timeout was removed.
153 pub fn persist(self: *KeyCommands, key: []const u8) ClientError!bool {
154 const result = try self.client.sendCommand(&.{ "PERSIST", key });
155 return switch (result) {
156 .integer => |i| i == 1,
157 else => false,
158 };
159 }
160
161 /// EXPIRETIME key - get Unix timestamp when key will expire (Redis 7+)
162 pub fn expireTime(self: *KeyCommands, key: []const u8) ClientError!?i64 {
163 const result = try self.client.sendCommand(&.{ "EXPIRETIME", key });
164 return switch (result) {
165 .integer => |i| if (i < 0) null else i,
166 else => null,
167 };
168 }
169
170 // ========================================================================
171 // Key Manipulation
172 // ========================================================================
173
174 /// RENAME key newkey - rename a key
175 pub fn rename(self: *KeyCommands, key: []const u8, new_key: []const u8) ClientError!void {
176 const result = try self.client.sendCommand(&.{ "RENAME", key, new_key });
177 if (result.isError()) return CommandError.KeyNotFound;
178 }
179
180 /// RENAMENX key newkey - rename only if newkey doesn't exist
181 /// Returns true if renamed.
182 pub fn renameNx(self: *KeyCommands, key: []const u8, new_key: []const u8) ClientError!bool {
183 const result = try self.client.sendCommand(&.{ "RENAMENX", key, new_key });
184 return switch (result) {
185 .integer => |i| i == 1,
186 else => false,
187 };
188 }
189
190 /// COPY source destination [REPLACE] - copy key to another key (Redis 6.2+)
191 pub fn copy(self: *KeyCommands, source: []const u8, dest: []const u8, replace: bool) ClientError!bool {
192 const result = if (replace)
193 try self.client.sendCommand(&.{ "COPY", source, dest, "REPLACE" })
194 else
195 try self.client.sendCommand(&.{ "COPY", source, dest });
196 return switch (result) {
197 .integer => |i| i == 1,
198 else => false,
199 };
200 }
201
202 // ========================================================================
203 // Key Scanning
204 // ========================================================================
205
206 /// Result from SCAN command
207 pub const ScanResult = struct {
208 cursor: []const u8,
209 keys: []const Value,
210 };
211
212 /// SCAN cursor [MATCH pattern] [COUNT count] - iterate keys
213 pub fn scan(self: *KeyCommands, cursor: []const u8, pattern: ?[]const u8, count: ?u32) ClientError!ScanResult {
214 // Build args dynamically
215 var args_buf: [7][]const u8 = undefined;
216 var arg_count: usize = 2;
217 args_buf[0] = "SCAN";
218 args_buf[1] = cursor;
219
220 if (pattern) |p| {
221 args_buf[arg_count] = "MATCH";
222 args_buf[arg_count + 1] = p;
223 arg_count += 2;
224 }
225
226 var count_buf: [16]u8 = undefined;
227 if (count) |c| {
228 args_buf[arg_count] = "COUNT";
229 args_buf[arg_count + 1] = std.fmt.bufPrint(&count_buf, "{d}", .{c}) catch unreachable;
230 arg_count += 2;
231 }
232
233 const result = try self.client.sendCommand(args_buf[0..arg_count]);
234 return switch (result) {
235 .array => |arr| {
236 if (arr.len < 2) return ScanResult{ .cursor = "0", .keys = &.{} };
237 return ScanResult{
238 .cursor = arr[0].asString() orelse "0",
239 .keys = arr[1].asArray() orelse &.{},
240 };
241 },
242 else => ScanResult{ .cursor = "0", .keys = &.{} },
243 };
244 }
245
246 /// KEYS pattern - find keys matching pattern
247 /// Warning: can be slow on large databases. Prefer SCAN for production.
248 pub fn keys(self: *KeyCommands, pattern: []const u8) ClientError![]const Value {
249 const result = try self.client.sendCommand(&.{ "KEYS", pattern });
250 return switch (result) {
251 .array => |a| a,
252 else => &.{},
253 };
254 }
255
256 /// RANDOMKEY - return a random key
257 pub fn randomKey(self: *KeyCommands) ClientError!?[]const u8 {
258 const result = try self.client.sendCommand(&.{"RANDOMKEY"});
259 return switch (result) {
260 .bulk => |b| b,
261 else => null,
262 };
263 }
264
265 // ========================================================================
266 // Serialization
267 // ========================================================================
268
269 /// DUMP key - serialize key's value
270 /// Returns serialized value or null if key doesn't exist.
271 pub fn dump(self: *KeyCommands, key: []const u8) ClientError!?[]const u8 {
272 const result = try self.client.sendCommand(&.{ "DUMP", key });
273 return switch (result) {
274 .bulk => |b| b,
275 else => null,
276 };
277 }
278
279 /// RESTORE key ttl serialized-value - restore from DUMP
280 pub fn restore(self: *KeyCommands, key: []const u8, ttl_ms: u64, serialized: []const u8, replace: bool) ClientError!void {
281 var ttl_buf: [24]u8 = undefined;
282 const ttl_str = std.fmt.bufPrint(&ttl_buf, "{d}", .{ttl_ms}) catch unreachable;
283
284 const result = if (replace)
285 try self.client.sendCommand(&.{ "RESTORE", key, ttl_str, serialized, "REPLACE" })
286 else
287 try self.client.sendCommand(&.{ "RESTORE", key, ttl_str, serialized });
288
289 if (result.isError()) return CommandError.RedisError;
290 }
291
292 /// OBJECT ENCODING key - get internal encoding of key's value
293 pub fn objectEncoding(self: *KeyCommands, key: []const u8) ClientError!?[]const u8 {
294 const result = try self.client.sendCommand(&.{ "OBJECT", "ENCODING", key });
295 return result.asString();
296 }
297
298 /// TOUCH key [key ...] - alters last access time, returns count touched
299 pub fn touch(self: *KeyCommands, key_list: []const []const u8) ClientError!i64 {
300 var args = try self.client.allocator.alloc([]const u8, key_list.len + 1);
301 defer self.client.allocator.free(args);
302 args[0] = "TOUCH";
303 @memcpy(args[1..], key_list);
304
305 const result = try self.client.sendCommand(args);
306 return switch (result) {
307 .integer => |i| i,
308 else => 0,
309 };
310 }
311};
312
313/// Extend Client with key commands.
314pub fn keyOps(client: *Client) KeyCommands {
315 return KeyCommands.init(client);
316}