From 73c82254f9d8ea8fca08a410ef7d2d673206fe56 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 4 Sep 2026 11:23:34 +0200 Subject: [PATCH] feat: add openTemp to expr --- src/expr.zig | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/expr.zig b/src/expr.zig index 0d9060e..06acd10 100644 --- a/src/expr.zig +++ b/src/expr.zig @@ -478,3 +478,90 @@ test "in and between reject malformed input" { try std.testing.expectError(error.InvalidValue, parse(allocator, "age between i:1 and i:notanumber")); } +fn openTemp(tmp: *std.testing.TmpDir, allocator: Allocator) !dbmod.Db { + const dbPath = try tmp.dir.realpathAlloc(allocator, "."); + defer allocator.free(dbPath); + return dbmod.Db.open(allocator, dbPath, .{}); +} + +test "a single-value in reaches Table.select through an index like eq does" { + 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 querymod.Table.open(&db, allocator, "cities"); + defer table.deinit(); + try table.createIndex("city"); + try table.put(.{ .int = 1 }, &.{.{ .name = "city", .value = .{ .text = "oslo" } }}); + try table.put(.{ .int = 2 }, &.{.{ .name = "city", .value = .{ .text = "bergen" } }}); + + var parsed = try parse(allocator, "city in (s:oslo)"); + defer parsed.deinit(); + + var plan = try table.explain(allocator, parsed.predicate); + defer plan.deinit(allocator); + try std.testing.expectEqual(querymod.PlanKind.index_eq, plan.kind); + try std.testing.expectEqualStrings("city", plan.indexName.?); + + const results = try table.select(parsed.predicate); + defer querymod.freeRows(allocator, results); + try std.testing.expectEqual(@as(usize, 1), results.len); + try std.testing.expectEqual(@as(i64, 1), results[0].get(querymod.pkField).?.int); +} + +test "a multi-value in falls back to a full scan and still matches every listed value" { + 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 querymod.Table.open(&db, allocator, "cities"); + defer table.deinit(); + try table.createIndex("city"); + try table.put(.{ .int = 1 }, &.{.{ .name = "city", .value = .{ .text = "oslo" } }}); + try table.put(.{ .int = 2 }, &.{.{ .name = "city", .value = .{ .text = "bergen" } }}); + try table.put(.{ .int = 3 }, &.{.{ .name = "city", .value = .{ .text = "trondheim" } }}); + + var parsed = try parse(allocator, "city in (s:oslo s:trondheim)"); + defer parsed.deinit(); + + var plan = try table.explain(allocator, parsed.predicate); + defer plan.deinit(allocator); + try std.testing.expectEqual(querymod.PlanKind.full_scan, plan.kind); + + const results = try table.select(parsed.predicate); + defer querymod.freeRows(allocator, results); + try std.testing.expectEqual(@as(usize, 2), results.len); +} + +test "between reaches Table.select through an index range like gte/lte does" { + 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 querymod.Table.open(&db, allocator, "people"); + defer table.deinit(); + try table.createIndex("age"); + var i: i64 = 0; + while (i < 10) : (i += 1) { + try table.put(.{ .int = i }, &.{.{ .name = "age", .value = .{ .int = i * 10 } }}); + } + + var parsed = try parse(allocator, "age between i:20 and i:50"); + defer parsed.deinit(); + + var plan = try table.explain(allocator, parsed.predicate); + defer plan.deinit(allocator); + try std.testing.expectEqual(querymod.PlanKind.index_range, plan.kind); + try std.testing.expectEqualStrings("age", plan.indexName.?); + + const results = try table.select(parsed.predicate); + defer querymod.freeRows(allocator, results); + // ages 20, 30, 40, 50 - inclusive both ends. + try std.testing.expectEqual(@as(usize, 4), results.len); +}