feat: flesh out query
This commit is contained in:
1 parent
ca52ad70f7
commit
c4c651f782
1 file changed
+159
+159
@@ -2065,3 +2065,162 @@ test "selectPageOrdered with an empty order-by falls through to selectPage's ear
|
||||
for (ordered, plain) |o, p| try std.testing.expectEqual(o.get(pkField).?.int, p.get(pkField).?.int);
|
||||
}
|
||||
|
||||
test "createCompositeIndex backfills then full-tuple eq select uses it" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
var table = try Table.open(&db, allocator, "users");
|
||||
defer table.deinit();
|
||||
|
||||
try table.put(.{ .int = 1 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = 30 } },
|
||||
});
|
||||
try table.put(.{ .int = 2 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = 40 } },
|
||||
});
|
||||
try table.put(.{ .int = 3 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "krakow" } },
|
||||
.{ .name = "age", .value = .{ .int = 30 } },
|
||||
});
|
||||
|
||||
try table.createCompositeIndex("city_age", &.{ "city", "age" });
|
||||
try std.testing.expect(table.hasCompositeIndex("city_age"));
|
||||
|
||||
const preds = [_]Predicate{
|
||||
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||
.{ .eq = .{ .field = "age", .value = .{ .int = 30 } } },
|
||||
};
|
||||
const results = try table.select(.{ .and_ = &preds });
|
||||
defer freeRows(allocator, results);
|
||||
try std.testing.expectEqual(@as(usize, 1), results.len);
|
||||
try std.testing.expectEqual(@as(i64, 1), results[0].get(pkField).?.int);
|
||||
}
|
||||
|
||||
test "composite index leftmost prefix narrows without constraining every column" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
var table = try Table.open(&db, allocator, "users");
|
||||
defer table.deinit();
|
||||
try table.createCompositeIndex("city_age", &.{ "city", "age" });
|
||||
|
||||
try table.put(.{ .int = 1 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = 30 } },
|
||||
});
|
||||
try table.put(.{ .int = 2 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = 40 } },
|
||||
});
|
||||
try table.put(.{ .int = 3 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "krakow" } },
|
||||
.{ .name = "age", .value = .{ .int = 50 } },
|
||||
});
|
||||
|
||||
// Only the leading column is constrained - the composite index must
|
||||
// still narrow the scan to the two warsaw rows via its leftmost prefix,
|
||||
// and the (unindexed-alone) age column is unconstrained here.
|
||||
const results = try table.select(.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
|
||||
defer freeRows(allocator, results);
|
||||
try std.testing.expectEqual(@as(usize, 2), results.len);
|
||||
}
|
||||
|
||||
test "composite index uses a range bound on the column after the matched prefix" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
var table = try Table.open(&db, allocator, "users");
|
||||
defer table.deinit();
|
||||
try table.createCompositeIndex("city_age", &.{ "city", "age" });
|
||||
|
||||
var age: i64 = 20;
|
||||
while (age < 30) : (age += 1) {
|
||||
try table.put(.{ .int = age }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = age } },
|
||||
});
|
||||
try table.put(.{ .int = age + 100 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "krakow" } },
|
||||
.{ .name = "age", .value = .{ .int = age } },
|
||||
});
|
||||
}
|
||||
|
||||
// city == "warsaw" and 23 <= age < 27: must stay inside the warsaw
|
||||
// bucket of the composite index even though krakow rows overlap the
|
||||
// same age range byte-for-byte.
|
||||
const preds = [_]Predicate{
|
||||
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||
.{ .gte = .{ .field = "age", .value = .{ .int = 23 } } },
|
||||
.{ .lt = .{ .field = "age", .value = .{ .int = 27 } } },
|
||||
};
|
||||
const results = try table.select(.{ .and_ = &preds });
|
||||
defer freeRows(allocator, results);
|
||||
try std.testing.expectEqual(@as(usize, 4), results.len);
|
||||
for (results) |r| {
|
||||
try std.testing.expectEqualStrings("warsaw", r.get("city").?.text);
|
||||
try std.testing.expect(r.get("age").?.int >= 23 and r.get("age").?.int < 27);
|
||||
}
|
||||
|
||||
// A one-sided range (age > 27) narrows the same way.
|
||||
const openPreds = [_]Predicate{
|
||||
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||
.{ .gt = .{ .field = "age", .value = .{ .int = 27 } } },
|
||||
};
|
||||
const openResults = try table.select(.{ .and_ = &openPreds });
|
||||
defer freeRows(allocator, openResults);
|
||||
try std.testing.expectEqual(@as(usize, 2), openResults.len);
|
||||
for (openResults) |r| {
|
||||
try std.testing.expectEqualStrings("warsaw", r.get("city").?.text);
|
||||
try std.testing.expect(r.get("age").?.int > 27);
|
||||
}
|
||||
}
|
||||
|
||||
test "composite index stays in sync across update and delete" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
var table = try Table.open(&db, allocator, "users");
|
||||
defer table.deinit();
|
||||
try table.createCompositeIndex("city_age", &.{ "city", "age" });
|
||||
|
||||
const preds = [_]Predicate{
|
||||
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||
.{ .eq = .{ .field = "age", .value = .{ .int = 30 } } },
|
||||
};
|
||||
const pred = Predicate{ .and_ = &preds };
|
||||
|
||||
try table.put(.{ .int = 1 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = 30 } },
|
||||
});
|
||||
{
|
||||
const found = try table.select(pred);
|
||||
defer freeRows(allocator, found);
|
||||
try std.testing.expectEqual(@as(usize, 1), found.len);
|
||||
}
|
||||
|
||||
// Moving age away from 30 must drop the old composite entry.
|
||||
try table.put(.{ .int = 1 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = 31 } },
|
||||
});
|
||||
{
|
||||
const stale = try table.select(pred);
|
||||
defer freeRows(allocator, stale);
|
||||
try std.testing.expectEqual(@as(usize, 0), stale.len);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user