From 73b247d29d43a41dcd58a74834d0e11c110c325c Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 18:18:40 +0200 Subject: [PATCH] feat: add cmdBegin and cmdCommit to repl --- src/repl.zig | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/src/repl.zig b/src/repl.zig index 8f33308..f50c014 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -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; + } + } +