feat: extend query
This commit is contained in:
1 parent
4fa334072e
commit
cc86659511
1 file changed
+161
+161
@@ -1434,3 +1434,164 @@ test "like predicate filters by pattern and never uses an index on that field" {
|
||||
// notion of substring matching), never mistake this for an eq lookup.
|
||||
try table.createIndex("name");
|
||||
|
||||
try table.put(.{ .int = 1 }, &.{.{ .name = "name", .value = .{ .text = "Ada Lovelace" } }});
|
||||
try table.put(.{ .int = 2 }, &.{.{ .name = "name", .value = .{ .text = "Alan Turing" } }});
|
||||
try table.put(.{ .int = 3 }, &.{.{ .name = "name", .value = .{ .text = "Grace Hopper" } }});
|
||||
|
||||
{
|
||||
const results = try table.select(.{ .like = .{ .field = "name", .value = .{ .text = "A%" } } });
|
||||
defer freeRows(allocator, results);
|
||||
try std.testing.expectEqual(@as(usize, 2), results.len);
|
||||
}
|
||||
{
|
||||
const results = try table.select(.{ .like = .{ .field = "name", .value = .{ .text = "%ing" } } });
|
||||
defer freeRows(allocator, results);
|
||||
try std.testing.expectEqual(@as(usize, 1), results.len);
|
||||
try std.testing.expectEqualStrings("Alan Turing", results[0].get("name").?.text);
|
||||
}
|
||||
{
|
||||
const results = try table.select(.{ .like = .{ .field = "name", .value = .{ .text = "%z%" } } });
|
||||
defer freeRows(allocator, results);
|
||||
try std.testing.expectEqual(@as(usize, 0), results.len);
|
||||
}
|
||||
|
||||
var plan = try table.explain(allocator, .{ .like = .{ .field = "name", .value = .{ .text = "A%" } } });
|
||||
defer plan.deinit(allocator);
|
||||
try std.testing.expectEqual(PlanKind.full_scan, plan.kind);
|
||||
}
|
||||
|
||||
test "like combines with an indexed eq leaf inside and_ without breaking the index plan" {
|
||||
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.createIndex("city");
|
||||
|
||||
try table.put(.{ .int = 1 }, &.{ .{ .name = "city", .value = .{ .text = "warsaw" } }, .{ .name = "name", .value = .{ .text = "Ada" } } });
|
||||
try table.put(.{ .int = 2 }, &.{ .{ .name = "city", .value = .{ .text = "warsaw" } }, .{ .name = "name", .value = .{ .text = "Boris" } } });
|
||||
try table.put(.{ .int = 3 }, &.{ .{ .name = "city", .value = .{ .text = "krakow" } }, .{ .name = "name", .value = .{ .text = "Ada" } } });
|
||||
|
||||
const preds = [_]Predicate{
|
||||
.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } },
|
||||
.{ .like = .{ .field = "name", .value = .{ .text = "A%" } } },
|
||||
};
|
||||
const pred = Predicate{ .and_ = &preds };
|
||||
|
||||
// findIndexableLeaf must still pick the `eq` leaf on `city` for the
|
||||
// index scan even though `like` sits right next to it in the same and_;
|
||||
// selectByIndexPage then re-evaluates the whole predicate (including
|
||||
// `like`) against every index candidate.
|
||||
var plan = try table.explain(allocator, pred);
|
||||
defer plan.deinit(allocator);
|
||||
try std.testing.expectEqual(PlanKind.index_eq, plan.kind);
|
||||
try std.testing.expectEqualStrings("city", plan.indexName.?);
|
||||
|
||||
const results = try table.select(pred);
|
||||
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 "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.createIndex("city");
|
||||
|
||||
try table.put(.{ .int = 1 }, &.{.{ .name = "city", .value = .{ .text = "warsaw" } }});
|
||||
{
|
||||
const warsaw = try table.select(.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
|
||||
defer freeRows(allocator, warsaw);
|
||||
try std.testing.expectEqual(@as(usize, 1), warsaw.len);
|
||||
}
|
||||
|
||||
// Move the row to a different city: the old index entry must disappear.
|
||||
try table.put(.{ .int = 1 }, &.{.{ .name = "city", .value = .{ .text = "krakow" } }});
|
||||
{
|
||||
const warsaw = try table.select(.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
|
||||
defer freeRows(allocator, warsaw);
|
||||
try std.testing.expectEqual(@as(usize, 0), warsaw.len);
|
||||
const krakow = try table.select(.{ .eq = .{ .field = "city", .value = .{ .text = "krakow" } } });
|
||||
defer freeRows(allocator, krakow);
|
||||
try std.testing.expectEqual(@as(usize, 1), krakow.len);
|
||||
}
|
||||
|
||||
try table.delete(.{ .int = 1 });
|
||||
{
|
||||
const krakow = try table.select(.{ .eq = .{ .field = "city", .value = .{ .text = "krakow" } } });
|
||||
defer freeRows(allocator, krakow);
|
||||
try std.testing.expectEqual(@as(usize, 0), krakow.len);
|
||||
}
|
||||
}
|
||||
|
||||
test "dropIndex removes entries and falls back to full scan" {
|
||||
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.createIndex("city");
|
||||
try table.put(.{ .int = 1 }, &.{.{ .name = "city", .value = .{ .text = "warsaw" } }});
|
||||
|
||||
try table.dropIndex("city");
|
||||
try std.testing.expect(!table.hasIndex("city"));
|
||||
|
||||
// Still correct without the index, just via full scan.
|
||||
const results = try table.select(.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
|
||||
defer freeRows(allocator, results);
|
||||
try std.testing.expectEqual(@as(usize, 1), results.len);
|
||||
}
|
||||
|
||||
test "range predicate uses index and respects bounds" {
|
||||
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, "events");
|
||||
defer table.deinit();
|
||||
try table.createIndex("score");
|
||||
|
||||
var i: i64 = 0;
|
||||
while (i < 10) : (i += 1) {
|
||||
try table.put(.{ .int = i }, &.{.{ .name = "score", .value = .{ .int = i } }});
|
||||
}
|
||||
|
||||
const results = try table.select(.{ .gte = .{ .field = "score", .value = .{ .int = 7 } } });
|
||||
defer freeRows(allocator, results);
|
||||
try std.testing.expectEqual(@as(usize, 3), results.len);
|
||||
}
|
||||
|
||||
test "compound and predicate narrows via one indexed conjunct" {
|
||||
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.createIndex("city");
|
||||
|
||||
try table.put(.{ .int = 1 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = 20 } },
|
||||
});
|
||||
try table.put(.{ .int = 2 }, &.{
|
||||
.{ .name = "city", .value = .{ .text = "warsaw" } },
|
||||
.{ .name = "age", .value = .{ .int = 40 } },
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user