diff --git a/src/repl.zig b/src/repl.zig
index f50c014..999eef1 100644
--- a/src/repl.zig
+++ b/src/repl.zig
@@ -322,3 +322,146 @@ pub const Repl = struct {
}
}
+ /// `table select
[expr...] [--limit N] [--offset N] [--order-by
+ /// field[:desc][,field[:desc]...]]`: interactive counterpart of the
+ /// one-shot CLI's `table select` (main.zig's `runTable`) over
+ /// query.zig's `Table.selectPageOrdered`, same expr grammar (expr.zig)
+ /// and `--limit`/`--offset`/`--order-by` conventions.
+ fn cmdTableSelect(self: *Repl, args: []const []const u8, writer: anytype) !void {
+ if (args.len < 1) return ReplError.WrongArgCount;
+ var table = try Table.open(self.db, self.allocator, args[0]);
+ defer table.deinit();
+
+ const sel = try extractSelectFlags(self.allocator, args[1..]);
+ defer self.allocator.free(sel.rest);
+ defer self.allocator.free(sel.flags.orderBy);
+
+ const exprText = try std.mem.join(self.allocator, " ", sel.rest);
+ 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;
+
+ const rows = try table.selectPageOrdered(pred, .{ .offset = sel.flags.page.offset, .limit = sel.flags.page.limit }, sel.flags.orderBy);
+ defer querymod.freeRows(self.allocator, rows);
+ for (rows) |r| try printRow(writer, r);
+ }
+
+ /// `table explain [expr...]`: interactive counterpart of the
+ /// one-shot CLI's `table explain` (main.zig's `runTable`), reporting
+ /// which strategy `table select` would use for the same predicate
+ /// without running it - `query.Table.explain` over the same expr
+ /// grammar `cmdTableSelect` already uses.
+ fn cmdTableExplain(self: *Repl, args: []const []const u8, writer: anytype) !void {
+ if (args.len < 1) return ReplError.WrongArgCount;
+ var table = try Table.open(self.db, self.allocator, args[0]);
+ defer table.deinit();
+
+ const exprText = try std.mem.join(self.allocator, " ", args[1..]);
+ 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 plan = try table.explain(self.allocator, pred);
+ defer plan.deinit(self.allocator);
+ try printPlan(writer, plan);
+ }
+
+ /// `table update --set field=value[,field=value...] [expr...]`:
+ /// interactive counterpart of the one-shot CLI's `table update`
+ /// (main.zig's `runTable`) over `query.Table.updateWhere`, same
+ /// `--set`/expr grammar. Prints the number of rows updated.
+ fn cmdTableUpdate(self: *Repl, args: []const []const u8, writer: anytype) !void {
+ if (args.len < 1) return ReplError.WrongArgCount;
+ var table = try Table.open(self.db, self.allocator, args[0]);
+ defer table.deinit();
+
+ const setf = try extractSetFlag(self.allocator, args[1..]);
+ defer self.allocator.free(setf.sets);
+ defer self.allocator.free(setf.rest);
+
+ const exprText = try std.mem.join(self.allocator, " ", setf.rest);
+ 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;
+
+ const updated = try table.updateWhere(pred, setf.sets);
+ try writer.print("{d}\n", .{updated});
+ }
+
+ /// `table delete [expr...]`: interactive counterpart of the
+ /// one-shot CLI's `table delete` (main.zig's `runTable`) over
+ /// `query.Table.deleteWhere`, same expr grammar as `cmdTableSelect`/
+ /// `cmdTableExplain` (empty expr deletes every row). Prints the number
+ /// of rows deleted.
+ fn cmdTableDelete(self: *Repl, args: []const []const u8, writer: anytype) !void {
+ if (args.len < 1) return ReplError.WrongArgCount;
+ var table = try Table.open(self.db, self.allocator, args[0]);
+ defer table.deinit();
+
+ const exprText = try std.mem.join(self.allocator, " ", args[1..]);
+ 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;
+
+ const deleted = try table.deleteWhere(pred);
+ try writer.print("{d}\n", .{deleted});
+ }
+
+ /// `table join [--left|--right|--full]
+ /// [--limit N] [--offset N]`: interactive
+ /// counterpart of the one-shot CLI's `table join` over join.zig's four
+ /// join functions. With no flag, prints inner-join pairs (`L `
+ /// then `R `); `--left`/`--right`/`--full` print `L (nil)`/
+ /// `R (nil)` for the unmatched side exactly like the CLI.
+ /// `--limit`/`--offset` window the already-joined result through
+ /// `join.applyPage`, same convention as `table select`'s paging (0 or
+ /// omitted = unlimited). Predicates on either side are not exposed here
+ /// (same deliberately minimal CLI surface as `table join` itself - the
+ /// richer path is the `join.zig` library API, as its own tests cover).
+ fn cmdTableJoin(self: *Repl, args: []const []const u8, writer: anytype) !void {
+ const JoinMode = enum { inner, left, right, full };
+ var mode: JoinMode = .inner;
+ var positional = std.ArrayList([]const u8).init(self.allocator);
+ defer positional.deinit();
+ for (args) |a| {
+ if (std.mem.eql(u8, a, "--left")) {
+ mode = .left;
+ } else if (std.mem.eql(u8, a, "--right")) {
+ mode = .right;
+ } else if (std.mem.eql(u8, a, "--full")) {
+ mode = .full;
+ } else {
+ try positional.append(a);
+ }
+ }
+ const pf = try extractPageFlags(self.allocator, positional.items);
+ defer self.allocator.free(pf.rest);
+ if (pf.rest.len != 4) return ReplError.WrongArgCount;
+ const jargs = pf.rest;
+ const page = querymod.Page{ .offset = pf.page.offset, .limit = pf.page.limit };
+
+ var left = try Table.open(self.db, self.allocator, jargs[0]);
+ defer left.deinit();
+ var right = try Table.open(self.db, self.allocator, jargs[2]);
+ defer right.deinit();
+