feat: add cmdBegin and cmdCommit to repl

This commit is contained in:
dualfroz committed 2026-09-09 18:18:40 +02:00
1 parent 9425a74f56
commit 73b247d29d
1 file changed
+163
+163
View File
@@ -159,3 +159,166 @@ pub const Repl = struct {
return ReplError.UnknownCommand;
}
fn cmdBegin(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 0) return ReplError.WrongArgCount;
if (self.txn != null) return ReplError.TransactionAlreadyActive;
self.txn = Transaction.begin(self.db);
try writer.writeAll("OK\n");
}
fn cmdCommit(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 0) return ReplError.WrongArgCount;
if (self.txn == null) return ReplError.NoActiveTransaction;
try self.txn.?.commit();
self.discardTxnState();
try writer.writeAll("OK\n");
}
fn cmdRollback(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 0) return ReplError.WrongArgCount;
if (self.txn == null) return ReplError.NoActiveTransaction;
self.discardTxnState();
try writer.writeAll("OK\n");
}
fn cmdPut(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 2) return ReplError.WrongArgCount;
if (self.txn) |*t| {
try t.put(args[0], args[1]);
} else {
try self.db.put(args[0], args[1]);
}
try writer.writeAll("OK\n");
}
fn cmdGet(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 1) return ReplError.WrongArgCount;
const value = if (self.txn) |*t|
try t.get(self.allocator, args[0])
else
try self.db.get(self.allocator, args[0]);
defer if (value) |v| self.allocator.free(v);
if (value) |v| {
try writer.print("{s}\n", .{v});
} else {
try writer.writeAll("(nil)\n");
}
}
fn cmdDel(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 1) return ReplError.WrongArgCount;
if (self.txn) |*t| {
try t.delete(args[0]);
} else {
try self.db.delete(args[0]);
}
try writer.writeAll("OK\n");
}
fn cmdScan(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len > 1) return ReplError.WrongArgCount;
const prefix = if (args.len == 1) args[0] else "";
var iter = try self.db.scanPrefix(self.allocator, prefix);
defer iter.deinit();
while (try iter.next()) |entry| {
try writer.print("{s}\t{s}\n", .{ entry.key, entry.value });
}
}
/// `expire`/`persist`/`ttl`/`expire-sweep` mirror the one-shot CLI's
/// same-named commands (main.zig) over `ttl.zig`'s flat-key TTL layer.
/// Like `scan` above, these always act on the live, already-committed
/// `Db` regardless of any open transaction: a TTL marker lives under its
/// own keyspace tag, set/cleared directly against `self.db`, never
/// staged through `Transaction` - there is no OCC-safe way to make
/// "expire this key in N seconds" conditional on a commit that hasn't
/// happened yet, so (deliberately, like `coll`'s ops) this bypasses the
/// active transaction entirely rather than pretending to participate in
/// it.
fn cmdExpire(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 2) return ReplError.WrongArgCount;
const seconds = std.fmt.parseInt(u64, args[1], 10) catch return ReplError.InvalidArgument;
const existing = try self.db.get(self.allocator, args[0]) orelse return ReplError.NoSuchKey;
self.allocator.free(existing);
try ttlmod.setExpireSeconds(self.db, self.allocator, args[0], std.time.nanoTimestamp(), seconds);
try writer.writeAll("OK\n");
}
fn cmdPersist(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 1) return ReplError.WrongArgCount;
try ttlmod.clearExpire(self.db, self.allocator, args[0]);
try writer.writeAll("OK\n");
}
fn cmdTtl(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 1) return ReplError.WrongArgCount;
switch (try ttlmod.remaining(self.db, self.allocator, args[0], std.time.nanoTimestamp())) {
.noKey => try writer.writeAll("-2\n"),
.noExpiry => try writer.writeAll("-1\n"),
.seconds => |s| try writer.print("{d}\n", .{s}),
}
}
fn cmdExpireSweep(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 0) return ReplError.WrongArgCount;
const reaped = try ttlmod.sweepExpired(self.db, self.allocator, std.time.nanoTimestamp());
try writer.print("reaped: {d}\n", .{reaped});
}
fn cmdStats(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 0) return ReplError.WrongArgCount;
try self.db.stats().print(writer);
}
fn cmdFlush(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 0) return ReplError.WrongArgCount;
try self.db.flush();
try writer.writeAll("OK\n");
}
fn cmdCompact(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len != 0) return ReplError.WrongArgCount;
try self.db.compact();
try writer.writeAll("OK\n");
}
fn cmdTable(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len < 1) return ReplError.WrongArgCount;
const op = args[0];
const rest = args[1..];
if (std.mem.eql(u8, op, "select")) return self.cmdTableSelect(rest, writer);
if (std.mem.eql(u8, op, "explain")) return self.cmdTableExplain(rest, writer);
if (std.mem.eql(u8, op, "update")) return self.cmdTableUpdate(rest, writer);
if (std.mem.eql(u8, op, "delete")) return self.cmdTableDelete(rest, writer);
if (std.mem.eql(u8, op, "join")) return self.cmdTableJoin(rest, writer);
if (std.mem.eql(u8, op, "aggregate")) return self.cmdTableAggregate(rest, writer);
if (std.mem.eql(u8, op, "window")) return self.cmdTableWindow(rest, writer);
if (rest.len < 2) return ReplError.WrongArgCount;
const tableName = rest[0];
const pk = try Value.parseCli(rest[1]);
if (std.mem.eql(u8, op, "put")) {
var fields = std.ArrayList(Field).init(self.allocator);
defer fields.deinit();
for (rest[2..]) |raw| try fields.append(try parseField(raw));
try self.tablePut(tableName, pk, fields.items);
try writer.writeAll("OK\n");
} else if (std.mem.eql(u8, op, "get")) {
if (rest.len != 2) return ReplError.WrongArgCount;
var row = try self.tableGet(tableName, pk) orelse {
try writer.writeAll("(nil)\n");
return;
};
defer row.deinit();
try printRow(writer, row);
} else if (std.mem.eql(u8, op, "del")) {
if (rest.len != 2) return ReplError.WrongArgCount;
try self.tableDel(tableName, pk);
try writer.writeAll("OK\n");
} else {
return ReplError.UnknownCommand;
}
}