feat: add getOrOpenTableTxn and tablePut to repl

This commit is contained in:
dualfroz committed 2026-09-09 18:39:56 +02:00
1 parent 4211ffa5fa
commit 463ab71b9c
1 file changed
+165
+165
View File
@@ -623,3 +623,168 @@ pub const Repl = struct {
defer self.allocator.free(rangeFlag.rest);
const frame: windowmod.FrameMode = if (rangeFlag.range) .range else .rows;
const split = splitWindowArgs(rangeFlag.rest);
const exprText = try std.mem.join(self.allocator, " ", split.whereArgs);
defer self.allocator.free(exprText);
var parsed: ?exprmod.Parsed = null;
defer if (parsed) |*p| p.deinit();
if (exprText.len > 0) parsed = try exprmod.parse(self.allocator, exprText);
const pred: ?querymod.Predicate = if (parsed) |p| p.predicate else null;
var partitionBy: [][]const u8 = &.{};
defer self.allocator.free(partitionBy);
if (split.partitionArgs) |partitionArgs| {
if (partitionArgs.len != 1) return ReplError.InvalidArgument;
partitionBy = parsePartitionList(self.allocator, partitionArgs[0]) catch return ReplError.InvalidArgument;
}
var orderBy: []querymod.OrderBy = &.{};
defer self.allocator.free(orderBy);
if (split.orderArgs) |orderArgs| {
if (orderArgs.len != 1) return ReplError.InvalidArgument;
orderBy = parseOrderByList(self.allocator, orderArgs[0]) catch return ReplError.InvalidArgument;
}
const rows = try table.select(pred);
defer querymod.freeRows(self.allocator, rows);
const results = try windowmod.computeMany(self.allocator, rows, partitionBy, orderBy, specs, frame);
defer windowmod.freeManyResults(self.allocator, results);
const displayOrder = try windowmod.orderedIndices(self.allocator, rows, partitionBy, orderBy);
defer self.allocator.free(displayOrder);
const total = displayOrder.len;
const start = @min(flags.page.offset, total);
const end = if (flags.page.limit == 0) total else @min(start + flags.page.limit, total);
var i: usize = start;
while (i < end) : (i += 1) {
const rowIdx = displayOrder[i];
for (rows[rowIdx].fields.items, 0..) |f, j| {
if (j > 0) try writer.writeAll("\t");
try writer.print("{s}=", .{f.name});
try printValue(writer, f.value);
}
if (rows[rowIdx].fields.items.len > 0) try writer.writeAll("\t");
for (specs, 0..) |spec, specIdx| {
if (specIdx > 0) try writer.writeAll("\t");
try printWindowLabel(writer, spec);
try writer.writeAll("=");
try printValue(writer, results[rowIdx][specIdx]);
}
try writer.writeAll("\n");
}
}
fn getOrOpenTableTxn(self: *Repl, name: []const u8) !*TableTxn {
if (self.tableTxns.getPtr(name)) |existing| return existing;
var opened = try TableTxn.open(&self.txn.?, self.allocator, name);
errdefer opened.deinit();
const nameCopy = try self.allocator.dupe(u8, name);
errdefer self.allocator.free(nameCopy);
try self.tableTxns.put(nameCopy, opened);
return self.tableTxns.getPtr(name).?;
}
fn tablePut(self: *Repl, name: []const u8, pk: Value, fields: []const Field) !void {
if (self.txn != null) {
const tt = try self.getOrOpenTableTxn(name);
return tt.put(pk, fields);
}
var table = try Table.open(self.db, self.allocator, name);
defer table.deinit();
try table.put(pk, fields);
}
fn tableGet(self: *Repl, name: []const u8, pk: Value) !?Row {
if (self.txn != null) {
const tt = try self.getOrOpenTableTxn(name);
return tt.get(pk);
}
var table = try Table.open(self.db, self.allocator, name);
defer table.deinit();
return table.get(pk);
}
fn tableDel(self: *Repl, name: []const u8, pk: Value) !void {
if (self.txn != null) {
const tt = try self.getOrOpenTableTxn(name);
return tt.delete(pk);
}
var table = try Table.open(self.db, self.allocator, name);
defer table.deinit();
try table.delete(pk);
}
/// Runs `coll <kind> <op> [args...]` against the live `Db` - the
/// interactive counterpart of the one-shot CLI's `coll` command
/// (main.zig's `runColl`), same dispatch shape and lazy-expiry-on-
/// access behavior (every op below reads/writes a named collection as
/// `rest[0]`, so a collection whose TTL already elapsed is expired
/// before the op sees it, matching the CLI). Deliberately NOT routed
/// through the REPL's active `Transaction` the way `table`/plain
/// `put`/`get`/`del` are: `Counter.incrBy`/`List.pushLeft`/`ZSet.add`/
/// etc. already open and commit their OWN internal `Transaction` with
/// OCC retry per call (see collections.zig), so there is no staged,
/// uncommitted state here for `begin`/`commit` to bracket the way
/// `TableTxn` brackets table writes - every `coll` op is independently
/// durable the instant it returns, with or without an open REPL
/// transaction, exactly like `scan` above always sees committed state
/// only regardless of any in-flight transaction.
fn cmdColl(self: *Repl, args: []const []const u8, writer: anytype) !void {
if (args.len >= 1 and std.mem.eql(u8, args[0], "expire-sweep")) {
if (args.len != 1) return ReplError.WrongArgCount;
const reaped = try collttlmod.sweepExpired(self.db, self.allocator, std.time.nanoTimestamp());
try writer.print("reaped: {d}\n", .{reaped});
return;
}
if (args.len < 2) return ReplError.WrongArgCount;
const kind = CollKind.parse(args[0]) orelse return ReplError.UnknownCollectionKind;
const op = args[1];
const rest = args[2..];
if (std.mem.eql(u8, op, "expire") or std.mem.eql(u8, op, "ttl") or std.mem.eql(u8, op, "persist")) {
return self.cmdCollTtl(kind, op, rest, writer);
}
if (rest.len >= 1) {
_ = try collttlmod.expireIfDue(self.db, self.allocator, kind, rest[0], std.time.nanoTimestamp());
}
switch (kind) {
.counter => try self.cmdCollCounter(op, rest, writer),
.hash => try self.cmdCollHash(op, rest, writer),
.set => try self.cmdCollSet(op, rest, writer),
.list => try self.cmdCollList(op, rest, writer),
.zset => try self.cmdCollZSet(op, rest, writer),
}
}
/// `coll <kind> expire|ttl|persist <name>` and `coll expire-sweep`
/// mirror the CLI's `runCollTtl`/top-of-`runColl` special case over
/// collttl.zig - the collection-namespace counterpart of `cmdExpire`/
/// `cmdPersist`/`cmdTtl`/`cmdExpireSweep` above, same reasoning for
/// bypassing the active transaction (a whole collection's expiry is not
/// staged through `Transaction` either).
fn cmdCollTtl(self: *Repl, kind: CollKind, op: []const u8, args: []const []const u8, writer: anytype) !void {
if (std.mem.eql(u8, op, "expire")) {
if (args.len != 2) return ReplError.WrongArgCount;
const seconds = std.fmt.parseInt(u64, args[1], 10) catch return ReplError.InvalidArgument;
if (!try collectionsmod.collectionExists(self.db, self.allocator, kind, args[0])) return ReplError.NoSuchKey;
try collttlmod.setExpireSeconds(self.db, self.allocator, kind, args[0], std.time.nanoTimestamp(), seconds);
try writer.writeAll("OK\n");
} else if (std.mem.eql(u8, op, "persist")) {
if (args.len != 1) return ReplError.WrongArgCount;
try collttlmod.clearExpire(self.db, self.allocator, kind, args[0]);
try writer.writeAll("OK\n");
} else {
if (args.len != 1) return ReplError.WrongArgCount;
switch (try collttlmod.remaining(self.db, self.allocator, kind, 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}),
}
}
}