From f398f09cb884a0a55d9ad0f9d46a361b059c0ce8 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 7 Sep 2026 14:20:39 +0200 Subject: [PATCH] feat: extend main with JoinMode --- src/main.zig | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/src/main.zig b/src/main.zig index a9dd0d8..527f49e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1695,6 +1695,179 @@ fn runTable(allocator: std.mem.Allocator, stdout: anytype, stderr: anytype, db: } const pred: ?cobalt.Predicate = if (parsed) |p| p.predicate else null; + var plan = try table.explain(allocator, pred); + defer plan.deinit(allocator); + try printPlan(stdout, plan); + } else if (std.mem.eql(u8, sub, "update")) { + if (args.len < 1) return fail(stderr, "table update needs --set field=value[,field=value...] [expr...]"); + var table = try cobalt.Table.open(db, allocator, args[0]); + defer table.deinit(); + + const setf = extractSetFlag(allocator, args[1..]) catch |err| return fail(stderr, if (err == error.MissingSetFlag) + "table update requires --set field=value[,field=value...]" + else + "invalid --set value"); + defer allocator.free(setf.sets); + defer allocator.free(setf.rest); + + const exprText = try std.mem.join(allocator, " ", setf.rest); + defer allocator.free(exprText); + + var parsed: ?cobalt.expr.Parsed = null; + defer if (parsed) |*p| p.deinit(); + if (exprText.len > 0) { + parsed = cobalt.expr.parse(allocator, exprText) catch |err| return fail(stderr, @errorName(err)); + } + const pred: ?cobalt.Predicate = if (parsed) |p| p.predicate else null; + + const updated = table.updateWhere(pred, setf.sets) catch |err| return fail(stderr, @errorName(err)); + try stdout.print("{d}\n", .{updated}); + } else if (std.mem.eql(u8, sub, "delete")) { + if (args.len < 1) return fail(stderr, "table delete needs
[expr...]"); + var table = try cobalt.Table.open(db, allocator, args[0]); + defer table.deinit(); + + const exprText = try std.mem.join(allocator, " ", args[1..]); + defer allocator.free(exprText); + + var parsed: ?cobalt.expr.Parsed = null; + defer if (parsed) |*p| p.deinit(); + if (exprText.len > 0) { + parsed = cobalt.expr.parse(allocator, exprText) catch |err| return fail(stderr, @errorName(err)); + } + const pred: ?cobalt.Predicate = if (parsed) |p| p.predicate else null; + + const deleted = table.deleteWhere(pred) catch |err| return fail(stderr, @errorName(err)); + try stdout.print("{d}\n", .{deleted}); + } else if (std.mem.eql(u8, sub, "index")) { + if (args.len < 3) return fail(stderr, "table index needs create|drop
"); + const action = args[0]; + var positional = std.ArrayList([]const u8).init(allocator); + defer positional.deinit(); + var unique = false; + for (args[1..]) |a| { + if (std.mem.eql(u8, a, "--unique")) { + unique = true; + } else { + try positional.append(a); + } + } + if (positional.items.len < 2) return fail(stderr, "table index needs create|drop
"); + var table = try cobalt.Table.open(db, allocator, positional.items[0]); + defer table.deinit(); + const fields = positional.items[1..]; + if (std.mem.eql(u8, action, "create")) { + if (unique) { + if (fields.len != 1) return fail(stderr, "--unique only supports a single field, not a composite index"); + table.createUniqueIndex(fields[0]) catch |err| return fail(stderr, @errorName(err)); + } else if (fields.len == 1) { + table.createIndex(fields[0]) catch |err| return fail(stderr, @errorName(err)); + } else { + const name = try std.mem.join(allocator, "+", fields); + defer allocator.free(name); + table.createCompositeIndex(name, fields) catch |err| return fail(stderr, @errorName(err)); + } + try stdout.writeAll("OK\n"); + } else if (std.mem.eql(u8, action, "drop")) { + if (fields.len == 1) { + try table.dropIndex(fields[0]); + } else { + const name = try std.mem.join(allocator, "+", fields); + defer allocator.free(name); + try table.dropCompositeIndex(name); + } + try stdout.writeAll("OK\n"); + } else { + return fail(stderr, "unknown table index action (expected create/drop)"); + } + } else if (std.mem.eql(u8, sub, "fk")) { + if (args.len < 2) return fail(stderr, "table fk needs create|drop
..."); + const action = args[0]; + var table = try cobalt.Table.open(db, allocator, args[1]); + defer table.deinit(); + if (std.mem.eql(u8, action, "create")) { + if (args.len < 5) return fail(stderr, "table fk create needs
[restrict|cascade]"); + const onDelete: cobalt.index.OnDelete = if (args.len < 6 or std.mem.eql(u8, args[5], "restrict")) + .restrict + else if (std.mem.eql(u8, args[5], "cascade")) + .cascade + else + return fail(stderr, "table fk create: last argument must be restrict or cascade"); + table.createForeignKey(args[2], args[3], args[4], onDelete) catch |err| return fail(stderr, @errorName(err)); + try stdout.writeAll("OK\n"); + } else if (std.mem.eql(u8, action, "drop")) { + if (args.len < 3) return fail(stderr, "table fk drop needs
"); + try table.dropForeignKey(args[2]); + try stdout.writeAll("OK\n"); + } else { + return fail(stderr, "unknown table fk action (expected create/drop)"); + } + } else if (std.mem.eql(u8, sub, "check")) { + if (args.len < 2) return fail(stderr, "table check needs create|drop
..."); + const action = args[0]; + var table = try cobalt.Table.open(db, allocator, args[1]); + defer table.deinit(); + if (std.mem.eql(u8, action, "create")) { + if (args.len < 4) return fail(stderr, "table check create needs
"); + const source = try std.mem.join(allocator, " ", args[3..]); + defer allocator.free(source); + table.createCheckConstraint(args[2], source) catch |err| return fail(stderr, @errorName(err)); + try stdout.writeAll("OK\n"); + } else if (std.mem.eql(u8, action, "drop")) { + if (args.len < 3) return fail(stderr, "table check drop needs
"); + try table.dropCheckConstraint(args[2]); + try stdout.writeAll("OK\n"); + } else { + return fail(stderr, "unknown table check action (expected create/drop)"); + } + } else if (std.mem.eql(u8, sub, "join")) { + const JoinMode = enum { inner, left, right, full }; + var mode: JoinMode = .inner; + var positional = std.ArrayList([]const u8).init(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 = extractPageFlags(allocator, positional.items) catch return fail(stderr, "invalid --limit/--offset value"); + defer allocator.free(pf.rest); + if (pf.rest.len < 4) return fail(stderr, "table join needs [--left|--right|--full] [--limit N] [--offset N]"); + const jargs = pf.rest; + const page = cobalt.query.Page{ .offset = pf.page.offset, .limit = pf.page.limit }; + + var left = try cobalt.Table.open(db, allocator, jargs[0]); + defer left.deinit(); + var right = try cobalt.Table.open(db, allocator, jargs[2]); + defer right.deinit(); + + switch (mode) { + .inner => { + const joined = cobalt.join.innerJoin(allocator, &left, jargs[1], null, &right, jargs[3], null) catch |err| return fail(stderr, @errorName(err)); + const paged = cobalt.join.applyPage(cobalt.join.JoinedRow, allocator, joined, page) catch |err| return fail(stderr, @errorName(err)); + defer cobalt.join.freeJoined(allocator, paged); + for (paged) |pair| { + try stdout.writeAll("L "); + try printRow(stdout, pair.left); + try stdout.writeAll("R "); + try printRow(stdout, pair.right); + } + }, + .left => { + const joined = cobalt.join.leftOuterJoin(allocator, &left, jargs[1], null, &right, jargs[3], null) catch |err| return fail(stderr, @errorName(err)); + const paged = cobalt.join.applyPage(cobalt.join.OuterJoinedRow, allocator, joined, page) catch |err| return fail(stderr, @errorName(err)); + defer cobalt.join.freeOuterJoined(allocator, paged); + for (paged) |pair| { + try stdout.writeAll("L "); + try printRow(stdout, pair.left); + if (pair.right) |r| { + try stdout.writeAll("R "); } fn runVerify(allocator: std.mem.Allocator, stdout: anytype, dir: []const u8, count: usize) !void {