feat: add stageIndexRemove and openTemp to txn_table
This commit is contained in:
1 parent
f16fc2ab58
commit
ad38d08079
1 file changed
+160
@@ -310,3 +310,163 @@ pub const TableTxn = struct {
|
||||
try self.txn.put(key, pkBytes);
|
||||
}
|
||||
|
||||
fn stageIndexRemove(self: *TableTxn, field: []const u8, value: Value, pk: Value) !void {
|
||||
const key = try idx.indexEntryKey(self.allocator, self.name, field, value, pk);
|
||||
defer self.allocator.free(key);
|
||||
try self.txn.delete(key);
|
||||
}
|
||||
};
|
||||
|
||||
fn openTemp(tmp: *std.testing.TmpDir, allocator: Allocator) !Db {
|
||||
const dbPath = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(dbPath);
|
||||
return Db.open(allocator, dbPath, .{});
|
||||
}
|
||||
|
||||
test "TableTxn commits multiple rows atomically" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
var txn = Transaction.begin(&db);
|
||||
defer txn.deinit();
|
||||
|
||||
var t = try TableTxn.open(&txn, allocator, "accounts");
|
||||
defer t.deinit();
|
||||
|
||||
try t.put(.{ .int = 1 }, &.{.{ .name = "balance", .value = .{ .int = 100 } }});
|
||||
try t.put(.{ .int = 2 }, &.{.{ .name = "balance", .value = .{ .int = 50 } }});
|
||||
|
||||
// Nothing visible outside the transaction yet.
|
||||
const probeKey = try idx.rowKey(allocator, "accounts", .{ .int = 1 });
|
||||
defer allocator.free(probeKey);
|
||||
try std.testing.expect((try db.get(allocator, probeKey)) == null);
|
||||
|
||||
try txn.commit();
|
||||
|
||||
var plain = try querymod.Table.open(&db, allocator, "accounts");
|
||||
defer plain.deinit();
|
||||
var row1 = (try plain.get(.{ .int = 1 })).?;
|
||||
defer row1.deinit();
|
||||
var row2 = (try plain.get(.{ .int = 2 })).?;
|
||||
defer row2.deinit();
|
||||
try std.testing.expectEqual(@as(i64, 100), row1.get("balance").?.int);
|
||||
try std.testing.expectEqual(@as(i64, 50), row2.get("balance").?.int);
|
||||
}
|
||||
|
||||
test "TableTxn transfer is all-or-nothing on conflict" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
{
|
||||
var setup = try querymod.Table.open(&db, allocator, "accounts");
|
||||
defer setup.deinit();
|
||||
try setup.put(.{ .int = 1 }, &.{.{ .name = "balance", .value = .{ .int = 100 } }});
|
||||
try setup.put(.{ .int = 2 }, &.{.{ .name = "balance", .value = .{ .int = 50 } }});
|
||||
}
|
||||
|
||||
var txn = Transaction.begin(&db);
|
||||
defer txn.deinit();
|
||||
var t = try TableTxn.open(&txn, allocator, "accounts");
|
||||
defer t.deinit();
|
||||
|
||||
// Move 30 from account 1 to account 2, staged but not yet committed.
|
||||
var from = (try t.get(.{ .int = 1 })).?;
|
||||
defer from.deinit();
|
||||
var to = (try t.get(.{ .int = 2 })).?;
|
||||
defer to.deinit();
|
||||
try t.put(.{ .int = 1 }, &.{.{ .name = "balance", .value = .{ .int = from.get("balance").?.int - 30 } }});
|
||||
try t.put(.{ .int = 2 }, &.{.{ .name = "balance", .value = .{ .int = to.get("balance").?.int + 30 } }});
|
||||
|
||||
// An external writer touches account 1 before the transfer commits.
|
||||
{
|
||||
var external = try querymod.Table.open(&db, allocator, "accounts");
|
||||
defer external.deinit();
|
||||
try external.put(.{ .int = 1 }, &.{.{ .name = "balance", .value = .{ .int = 999 } }});
|
||||
}
|
||||
|
||||
try std.testing.expectError(error.Conflict, txn.commit());
|
||||
|
||||
// Neither leg of the transfer applied: account 2 must be untouched,
|
||||
// and account 1 must show the external writer's value, not the stale
|
||||
// transfer's.
|
||||
var verify = try querymod.Table.open(&db, allocator, "accounts");
|
||||
defer verify.deinit();
|
||||
var row1 = (try verify.get(.{ .int = 1 })).?;
|
||||
defer row1.deinit();
|
||||
var row2 = (try verify.get(.{ .int = 2 })).?;
|
||||
defer row2.deinit();
|
||||
try std.testing.expectEqual(@as(i64, 999), row1.get("balance").?.int);
|
||||
try std.testing.expectEqual(@as(i64, 50), row2.get("balance").?.int);
|
||||
}
|
||||
|
||||
test "TableTxn keeps a pre-existing index in sync across commit" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
{
|
||||
var setup = try querymod.Table.open(&db, allocator, "users");
|
||||
defer setup.deinit();
|
||||
try setup.put(.{ .int = 1 }, &.{.{ .name = "city", .value = .{ .text = "warsaw" } }});
|
||||
try setup.createIndex("city");
|
||||
}
|
||||
|
||||
var txn = Transaction.begin(&db);
|
||||
defer txn.deinit();
|
||||
var t = try TableTxn.open(&txn, allocator, "users");
|
||||
defer t.deinit();
|
||||
try std.testing.expect(t.hasIndex("city"));
|
||||
|
||||
try t.put(.{ .int = 1 }, &.{.{ .name = "city", .value = .{ .text = "krakow" } }});
|
||||
try t.put(.{ .int = 2 }, &.{.{ .name = "city", .value = .{ .text = "warsaw" } }});
|
||||
try txn.commit();
|
||||
|
||||
var plain = try querymod.Table.open(&db, allocator, "users");
|
||||
defer plain.deinit();
|
||||
const warsaw = try plain.select(.{ .eq = .{ .field = "city", .value = .{ .text = "warsaw" } } });
|
||||
defer querymod.freeRows(allocator, warsaw);
|
||||
try std.testing.expectEqual(@as(usize, 1), warsaw.len);
|
||||
try std.testing.expectEqual(@as(i64, 2), warsaw[0].get(pkField).?.int);
|
||||
|
||||
const krakow = try plain.select(.{ .eq = .{ .field = "city", .value = .{ .text = "krakow" } } });
|
||||
defer querymod.freeRows(allocator, krakow);
|
||||
try std.testing.expectEqual(@as(usize, 1), krakow.len);
|
||||
try std.testing.expectEqual(@as(i64, 1), krakow[0].get(pkField).?.int);
|
||||
}
|
||||
|
||||
test "TableTxn delete removes row and index entries only on commit" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
{
|
||||
var setup = try querymod.Table.open(&db, allocator, "users");
|
||||
defer setup.deinit();
|
||||
try setup.createIndex("city");
|
||||
try setup.put(.{ .int = 1 }, &.{.{ .name = "city", .value = .{ .text = "warsaw" } }});
|
||||
}
|
||||
|
||||
var txn = Transaction.begin(&db);
|
||||
defer txn.deinit();
|
||||
var t = try TableTxn.open(&txn, allocator, "users");
|
||||
defer t.deinit();
|
||||
try t.delete(.{ .int = 1 });
|
||||
|
||||
// Not yet committed: still visible outside the transaction.
|
||||
{
|
||||
var outside = try querymod.Table.open(&db, allocator, "users");
|
||||
defer outside.deinit();
|
||||
var stillThere = (try outside.get(.{ .int = 1 })).?;
|
||||
stillThere.deinit();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user