feat: extend join

This commit is contained in:
dualfroz committed 2026-09-06 16:01:00 +02:00
1 parent 03ad32ad97
commit 9351672fbd
1 file changed
+158
+158
View File
@@ -464,3 +464,161 @@ test "innerJoin: unindexed hash join matches on equal field value" {
try testing.expectEqual(@as(usize, 3), joined.len);
var aliceItems = std.StringHashMap(void).init(testing.allocator);
defer aliceItems.deinit();
var seenBob = false;
for (joined) |pair| {
const name = pair.left.get("name").?.text;
const item = pair.right.get("item").?.text;
if (std.mem.eql(u8, name, "alice")) {
try aliceItems.put(item, {});
} else if (std.mem.eql(u8, name, "bob")) {
seenBob = true;
try testing.expectEqualStrings("gizmo", item);
} else {
try testing.expect(false); // unreachable: only alice/bob should ever match
}
}
try testing.expect(aliceItems.contains("widget"));
try testing.expect(aliceItems.contains("gadget"));
try testing.expect(seenBob);
}
test "innerJoin: index nested-loop join produces identical results to hash join" {
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTestDb(&tmp);
defer db.close();
var customers = try Table.open(&db, testing.allocator, "customers");
defer customers.deinit();
try customers.put(.{ .int = 1 }, &.{.{ .name = "name", .value = .{ .text = "alice" } }});
try customers.put(.{ .int = 2 }, &.{.{ .name = "name", .value = .{ .text = "bob" } }});
var orders = try Table.open(&db, testing.allocator, "orders");
defer orders.deinit();
try orders.createIndex("customer_id");
try orders.put(.{ .int = 100 }, &.{.{ .name = "customer_id", .value = .{ .int = 1 } }});
try orders.put(.{ .int = 101 }, &.{.{ .name = "customer_id", .value = .{ .int = 1 } }});
try orders.put(.{ .int = 102 }, &.{.{ .name = "customer_id", .value = .{ .int = 2 } }});
try testing.expect(orders.hasIndex("customer_id"));
const joined = try innerJoin(testing.allocator, &customers, "__pk", null, &orders, "customer_id", null);
defer freeJoined(testing.allocator, joined);
try testing.expectEqual(@as(usize, 3), joined.len);
}
test "innerJoin: rightPred filters matches out even when the join key matches" {
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTestDb(&tmp);
defer db.close();
var customers = try Table.open(&db, testing.allocator, "customers");
defer customers.deinit();
try customers.put(.{ .int = 1 }, &.{});
var orders = try Table.open(&db, testing.allocator, "orders");
defer orders.deinit();
try orders.put(.{ .int = 100 }, &.{ .{ .name = "customer_id", .value = .{ .int = 1 } }, .{ .name = "status", .value = .{ .text = "shipped" } } });
try orders.put(.{ .int = 101 }, &.{ .{ .name = "customer_id", .value = .{ .int = 1 } }, .{ .name = "status", .value = .{ .text = "cancelled" } } });
const shippedOnly = Predicate{ .eq = .{ .field = "status", .value = .{ .text = "shipped" } } };
const joined = try innerJoin(testing.allocator, &customers, "__pk", null, &orders, "customer_id", shippedOnly);
defer freeJoined(testing.allocator, joined);
try testing.expectEqual(@as(usize, 1), joined.len);
try testing.expectEqualStrings("shipped", joined[0].right.get("status").?.text);
}
test "innerJoin: rightPred filters matches out even through an index nested-loop join" {
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTestDb(&tmp);
defer db.close();
var customers = try Table.open(&db, testing.allocator, "customers");
defer customers.deinit();
try customers.put(.{ .int = 1 }, &.{});
var orders = try Table.open(&db, testing.allocator, "orders");
defer orders.deinit();
try orders.createIndex("customer_id");
try orders.put(.{ .int = 100 }, &.{ .{ .name = "customer_id", .value = .{ .int = 1 } }, .{ .name = "status", .value = .{ .text = "shipped" } } });
try orders.put(.{ .int = 101 }, &.{ .{ .name = "customer_id", .value = .{ .int = 1 } }, .{ .name = "status", .value = .{ .text = "cancelled" } } });
const shippedOnly = Predicate{ .eq = .{ .field = "status", .value = .{ .text = "shipped" } } };
const joined = try innerJoin(testing.allocator, &customers, "__pk", null, &orders, "customer_id", shippedOnly);
defer freeJoined(testing.allocator, joined);
try testing.expectEqual(@as(usize, 1), joined.len);
try testing.expectEqualStrings("shipped", joined[0].right.get("status").?.text);
}
test "innerJoin: leftPred narrows which left rows are even probed" {
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTestDb(&tmp);
defer db.close();
var customers = try Table.open(&db, testing.allocator, "customers");
defer customers.deinit();
try customers.put(.{ .int = 1 }, &.{.{ .name = "vip", .value = .{ .boolean = true } }});
try customers.put(.{ .int = 2 }, &.{.{ .name = "vip", .value = .{ .boolean = false } }});
var orders = try Table.open(&db, testing.allocator, "orders");
defer orders.deinit();
try orders.put(.{ .int = 100 }, &.{.{ .name = "customer_id", .value = .{ .int = 1 } }});
try orders.put(.{ .int = 101 }, &.{.{ .name = "customer_id", .value = .{ .int = 2 } }});
const vipOnly = Predicate{ .eq = .{ .field = "vip", .value = .{ .boolean = true } } };
const joined = try innerJoin(testing.allocator, &customers, "__pk", vipOnly, &orders, "customer_id", null);
defer freeJoined(testing.allocator, joined);
try testing.expectEqual(@as(usize, 1), joined.len);
}
test "innerJoin: mismatched types never match even with equal-looking payloads" {
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTestDb(&tmp);
defer db.close();
var left = try Table.open(&db, testing.allocator, "left");
defer left.deinit();
try left.put(.{ .int = 1 }, &.{.{ .name = "k", .value = .{ .int = 5 } }});
var right = try Table.open(&db, testing.allocator, "right");
defer right.deinit();
try right.put(.{ .int = 1 }, &.{.{ .name = "k", .value = .{ .float = 5.0 } }});
const joined = try innerJoin(testing.allocator, &left, "k", null, &right, "k", null);
defer freeJoined(testing.allocator, joined);
try testing.expectEqual(@as(usize, 0), joined.len);
}
test "innerJoin: rows missing the join field on either side never match" {
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTestDb(&tmp);
defer db.close();
var left = try Table.open(&db, testing.allocator, "left");
defer left.deinit();
try left.put(.{ .int = 1 }, &.{}); // no "k" field at all
try left.put(.{ .int = 2 }, &.{.{ .name = "k", .value = .{ .int = 7 } }});
var right = try Table.open(&db, testing.allocator, "right");
defer right.deinit();
try right.put(.{ .int = 1 }, &.{.{ .name = "k", .value = .{ .int = 7 } }});
const joined = try innerJoin(testing.allocator, &left, "k", null, &right, "k", null);
defer freeJoined(testing.allocator, joined);
try testing.expectEqual(@as(usize, 1), joined.len);
try testing.expectEqual(@as(i64, 2), joined[0].left.get("__pk").?.int);
}