feat: expand join
This commit is contained in:
1 parent
9351672fbd
commit
c03d2ad2cd
1 file changed
+161
+161
@@ -622,3 +622,164 @@ test "innerJoin: rows missing the join field on either side never match" {
|
||||
try testing.expectEqual(@as(i64, 2), joined[0].left.get("__pk").?.int);
|
||||
}
|
||||
|
||||
test "innerJoin: empty right table yields zero matches without touching left side output" {
|
||||
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 = 1 } }});
|
||||
|
||||
var right = try Table.open(&db, testing.allocator, "right");
|
||||
defer right.deinit();
|
||||
|
||||
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: one-to-many fan-out duplicates the left row for each right 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 }, &.{.{ .name = "k", .value = .{ .int = 42 } }});
|
||||
|
||||
var right = try Table.open(&db, testing.allocator, "right");
|
||||
defer right.deinit();
|
||||
var i: i64 = 0;
|
||||
while (i < 5) : (i += 1) {
|
||||
try right.put(.{ .int = i }, &.{.{ .name = "k", .value = .{ .int = 42 } }});
|
||||
}
|
||||
|
||||
const joined = try innerJoin(testing.allocator, &left, "k", null, &right, "k", null);
|
||||
defer freeJoined(testing.allocator, joined);
|
||||
|
||||
try testing.expectEqual(@as(usize, 5), joined.len);
|
||||
for (joined) |pair| {
|
||||
try testing.expectEqual(@as(i64, 1), pair.left.get("__pk").?.int);
|
||||
}
|
||||
}
|
||||
|
||||
test "leftOuterJoin: unmatched left row survives with right=null instead of vanishing" {
|
||||
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" } }}); // never ordered
|
||||
|
||||
var orders = try Table.open(&db, testing.allocator, "orders");
|
||||
defer orders.deinit();
|
||||
try orders.put(.{ .int = 100 }, &.{.{ .name = "customer_id", .value = .{ .int = 1 } }});
|
||||
|
||||
const joined = try leftOuterJoin(testing.allocator, &customers, "__pk", null, &orders, "customer_id", null);
|
||||
defer freeOuterJoined(testing.allocator, joined);
|
||||
|
||||
try testing.expectEqual(@as(usize, 2), joined.len);
|
||||
var sawAliceMatched = false;
|
||||
var sawBobUnmatched = false;
|
||||
for (joined) |pair| {
|
||||
const name = pair.left.get("name").?.text;
|
||||
if (std.mem.eql(u8, name, "alice")) {
|
||||
try testing.expect(pair.right != null);
|
||||
sawAliceMatched = true;
|
||||
} else if (std.mem.eql(u8, name, "bob")) {
|
||||
try testing.expect(pair.right == null);
|
||||
sawBobUnmatched = true;
|
||||
}
|
||||
}
|
||||
try testing.expect(sawAliceMatched);
|
||||
try testing.expect(sawBobUnmatched);
|
||||
}
|
||||
|
||||
test "leftOuterJoin: 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 }, &.{});
|
||||
try customers.put(.{ .int = 2 }, &.{}); // no matching order either way
|
||||
|
||||
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 testing.expect(orders.hasIndex("customer_id"));
|
||||
|
||||
const joined = try leftOuterJoin(testing.allocator, &customers, "__pk", null, &orders, "customer_id", null);
|
||||
defer freeOuterJoined(testing.allocator, joined);
|
||||
|
||||
// customer 1 matches twice, customer 2 matches zero times but still appears once.
|
||||
try testing.expectEqual(@as(usize, 3), joined.len);
|
||||
var unmatchedCount: usize = 0;
|
||||
for (joined) |pair| {
|
||||
if (pair.right == null) unmatchedCount += 1;
|
||||
}
|
||||
try testing.expectEqual(@as(usize, 1), unmatchedCount);
|
||||
}
|
||||
|
||||
test "leftOuterJoin: rightPred filtering out the only match still keeps the left row with right=null" {
|
||||
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 = "cancelled" } } });
|
||||
|
||||
const shippedOnly = Predicate{ .eq = .{ .field = "status", .value = .{ .text = "shipped" } } };
|
||||
const joined = try leftOuterJoin(testing.allocator, &customers, "__pk", null, &orders, "customer_id", shippedOnly);
|
||||
defer freeOuterJoined(testing.allocator, joined);
|
||||
|
||||
try testing.expectEqual(@as(usize, 1), joined.len);
|
||||
try testing.expect(joined[0].right == null);
|
||||
}
|
||||
|
||||
test "leftOuterJoin: left row missing the join field entirely still appears once with right=null" {
|
||||
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 leftOuterJoin(testing.allocator, &left, "k", null, &right, "k", null);
|
||||
defer freeOuterJoined(testing.allocator, joined);
|
||||
|
||||
try testing.expectEqual(@as(usize, 2), joined.len);
|
||||
for (joined) |pair| {
|
||||
if (pair.left.get("__pk").?.int == 1) {
|
||||
try testing.expect(pair.right == null);
|
||||
} else {
|
||||
try testing.expect(pair.right != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user