Files
bun.sh/src/valkey2/command.zig
Marko Vejnovic 9aa2a53be1 Implement expire
2025-10-08 10:49:04 -07:00

291 lines
6.0 KiB
Zig

pub const CommandDescriptor = enum {
APPEND,
BITCOUNT,
HMSET,
HMGET,
BLMOVE,
BLMPOP,
HINCRBY,
BLPOP,
BRPOP,
BZMPOP,
BZPOPMAX,
BZPOPMIN,
COPY,
DECR,
DECRBY,
DEL,
DUMP,
EXISTS,
EXPIRE,
EXPIREAT,
EXPIRETIME,
GET,
GETBIT,
GETDEL,
GETEX,
GETSET,
HDEL,
HELLO,
HGET,
HGETALL,
HINCRBYFLOAT,
HKEYS,
HLEN,
HRANDFIELD,
HSCAN,
HSTRLEN,
HVALS,
INCR,
INCRBY,
INCRBYFLOAT,
KEYS,
LINDEX,
LINSERT,
LLEN,
LMOVE,
LMPOP,
LPOP,
LPOS,
LPUSH,
LPUSHX,
MGET,
MSET,
MSETNX,
PERSIST,
PEXPIRE,
PEXPIREAT,
PEXPIRETIME,
RPUSH,
RPUSHX,
ZMSCORE,
ZREMRANGEBYLEX,
ZREMRANGEBYRANK,
ZREMRANGEBYSCORE,
PFADD,
PING,
PTTL,
RANDOMKEY,
RENAME,
RENAMENX,
RPOP,
RPOPLPUSH,
SCAN,
SCARD,
SCRIPT,
SDIFF,
SDIFFSTORE,
SELECT,
SET,
SETNX,
SINTER,
SINTERCARD,
SINTERSTORE,
SMISMEMBER,
SPUBLISH,
SSCAN,
STRLEN,
SUNION,
SUNIONSTORE,
TOUCH,
TYPE,
UNLINK,
ZADD,
ZCARD,
ZDIFF,
ZDIFFSTORE,
ZINTER,
ZINTERCARD,
ZINTERSTORE,
ZMPOP,
ZPOPMAX,
ZPOPMIN,
ZRANDMEMBER,
ZRANGE,
ZRANGEBYLEX,
ZRANGEBYSCORE,
ZRANGESTORE,
ZRANK,
ZREM,
ZREVRANGE,
ZREVRANGEBYLEX,
ZREVRANGEBYSCORE,
ZREVRANK,
ZSCAN,
ZSCORE,
ZUNION,
ZUNIONSTORE,
BRPOPLPUSH,
TTL,
SREM,
SETBIT,
GETRANGE,
SETRANGE,
LRANGE,
LREM,
LSET,
LTRIM,
SMEMBERS,
ZCOUNT,
ZLEXCOUNT,
SETEX,
PSETEX,
ZINCRBY,
SUBSTR,
SISMEMBER,
HTTL,
SMOVE,
SADD,
SPOP,
SRANDMEMBER,
HSETNX,
HPTTL,
HGETDEL,
HGETEX,
HSETEX,
HSET,
HEXPIRE,
HEXPIREAT,
HEXPIRETIME,
HPERSIST,
HPEXPIRE,
HPEXPIREAT,
HPEXPIRETIME,
// TODO(markovejnovic): Test this better
HEXISTS,
pub fn returnsBool(self: CommandDescriptor) bool {
return switch (self) {
.EXISTS, .SISMEMBER, .HSETNX, .HEXISTS, .SMOVE => true,
else => false,
};
}
pub fn toString(self: CommandDescriptor) []const u8 {
return switch (self) {
else => |enum_value| @tagName(enum_value),
};
}
/// Whether this command can be pipelined or not.
///
/// This is pretty important as some commands cannot really be pipelined and require flushing
/// any pending commands before executing them.
pub fn canBePipelined(self: CommandDescriptor) bool {
return switch (self) {
.HELLO => false,
else => true,
};
}
};
pub const Command = struct {
const Self = @This();
command: union(enum) {
inline_str: []const u8,
command_id: CommandDescriptor,
/// Test whether this command is one which returns a boolean value.
pub fn returnsBool(self: @This()) bool {
return switch (self) {
.inline_str => |_| false,
.command_id => |id| id.returnsBool(),
};
}
pub fn toString(self: @This()) []const u8 {
return switch (self) {
.inline_str => |s| s,
.command_id => |id| id.toString(),
};
}
},
args: CommandArgs,
pub fn initDirect(command: []const u8, args: CommandArgs) Self {
return Self{ .command = .{ .inline_str = command }, .args = args };
}
pub fn initById(command: CommandDescriptor, args: CommandArgs) Self {
return Self{ .command = .{ .command_id = command }, .args = args };
}
pub fn serialize(self: *const Self, allocator: std.mem.Allocator) ![]u8 {
var buf = try std.ArrayList(u8).initCapacity(allocator, self.byteLength());
errdefer buf.deinit();
try self.write(buf.writer());
return buf.items;
}
pub fn format(
this: Self,
comptime _: []const u8,
_: std.fmt.FormatOptions,
writer: anytype,
) !void {
try this.write(writer);
}
pub fn byteLength(self: *const Self) usize {
return std.fmt.count("{}", .{self.*});
}
pub fn canBePipelined(self: *const Self) bool {
return switch (self.command) {
// TODO(markovejnovic): This doesn't make too much sense to me since we don't know what
// the command is. Maybe we should assume the worst and say it can't be pipelined?
// However, this was the legacy behavior so I decided not to change it for now.
.inline_str => |_| return true,
.command_id => |id| return id.canBePipelined(),
};
}
pub fn returnsBool(self: *const Self) bool {
return self.command.returnsBool();
}
/// Write the command in RESP format to the given writer
pub fn write(this: *const Self, writer: anytype) !void {
try writer.print("*{d}\r\n", .{1 + this.args.len()});
try writer.print("${d}\r\n{s}\r\n", .{
this.command.toString().len,
this.command.toString(),
});
switch (this.args) {
inline .slices, .args => |args| {
for (args) |*arg| {
try writer.print(
"${d}\r\n{s}\r\n",
.{ arg.byteLength(), arg.slice() },
);
}
},
.raw => |args| {
for (args) |arg| {
try writer.print("${d}\r\n{s}\r\n", .{ arg.len, arg });
}
},
}
}
};
pub const CommandArgs = union(enum) {
slices: []const bun.jsc.ZigString.Slice,
args: []const bun.api.node.BlobOrStringOrBuffer,
raw: []const []const u8,
pub fn len(this: *const @This()) usize {
return switch (this.*) {
inline .slices, .args, .raw => |args| args.len,
};
}
};
const bun = @import("bun");
const std = @import("std");