|
@@ -0,0 +1,115 @@
|
|
|
|
|
+const std = @import("std");
|
|
|
|
|
+
|
|
|
|
|
+var wal_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
|
|
|
+const wal_allocator = wal_arena.allocator();
|
|
|
|
|
+
|
|
|
|
|
+pub fn compactUPKVFile(filename: []const u8) !void {
|
|
|
|
|
+ const extension = std.fs.path.extension(filename);
|
|
|
|
|
+ if (!std.mem.eql(u8, extension, "upkv")) {
|
|
|
|
|
+ std.debug.print("Invalid file extension: {s}\n", .{extension});
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const records = try getRecordsFromFile(filename);
|
|
|
|
|
+ const compactedRecords = compact(records);
|
|
|
|
|
+ for (compactedRecords) |record| {
|
|
|
|
|
+ std.debug.print("{s}\n", .{record});
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+test "compactUPKVFile test" {}
|
|
|
|
|
+
|
|
|
|
|
+pub fn getRecordsFromFile(filename: []const u8) ![]const []const u8 {
|
|
|
|
|
+ const cwd = std.fs.cwd();
|
|
|
|
|
+ var upkv: ?std.fs.File = null;
|
|
|
|
|
+
|
|
|
|
|
+ upkv = cwd.openFile(filename, .{ .mode = .read_only }) catch |err| {
|
|
|
|
|
+ std.debug.print("Failed to open file: {any}\n", .{err});
|
|
|
|
|
+ return err;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const upkvData = upkv.?.readToEndAlloc(wal_allocator, 10_000_000 * 100) catch |err| {
|
|
|
|
|
+ std.debug.print("Failed to read file: {any}\n", .{err});
|
|
|
|
|
+ return err;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ var records = std.ArrayListUnmanaged([]const u8){};
|
|
|
|
|
+ var it = std.mem.splitScalar(u8, upkvData, '\r');
|
|
|
|
|
+ while (it.next()) |record| {
|
|
|
|
|
+ if (record.len > 0) {
|
|
|
|
|
+ try records.append(wal_allocator, record);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return records.items;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+test "getRecordsFromFile test" {
|
|
|
|
|
+ var tmp_dir = std.testing.tmpDir(.{});
|
|
|
|
|
+ defer tmp_dir.cleanup();
|
|
|
|
|
+
|
|
|
|
|
+ const file = try tmp_dir.dir.createFile("test.upkv", .{});
|
|
|
|
|
+ defer file.close();
|
|
|
|
|
+ try file.writeAll("W|key1|value1\rD|key2|\rW|key3|value3");
|
|
|
|
|
+
|
|
|
|
|
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
|
|
|
+ const tmp_path = try tmp_dir.dir.realpath(".", &path_buf);
|
|
|
|
|
+ const full_path = try std.fmt.allocPrint(std.testing.allocator, "{s}/test.upkv", .{tmp_path});
|
|
|
|
|
+ defer std.testing.allocator.free(full_path);
|
|
|
|
|
+
|
|
|
|
|
+ const records = try getRecordsFromFile(full_path);
|
|
|
|
|
+ try std.testing.expect(records.len == 3);
|
|
|
|
|
+ try std.testing.expectEqualStrings("W|key1|value1", records[0]);
|
|
|
|
|
+ try std.testing.expectEqualStrings("D|key2|", records[1]);
|
|
|
|
|
+ try std.testing.expectEqualStrings("W|key3|value3", records[2]);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+pub fn compact(records: []const []const u8) []const []const u8 {
|
|
|
|
|
+ var compactMap = std.StringHashMap([]const u8).init(wal_allocator);
|
|
|
|
|
+
|
|
|
|
|
+ for (records) |record| {
|
|
|
|
|
+ var parts = std.mem.splitScalar(u8, record, '|');
|
|
|
|
|
+
|
|
|
|
|
+ const opcode = parts.next() orelse continue;
|
|
|
|
|
+ const key = parts.next() orelse continue;
|
|
|
|
|
+ const value = parts.next() orelse "";
|
|
|
|
|
+
|
|
|
|
|
+ if (std.mem.eql(u8, opcode, "D")) {
|
|
|
|
|
+ _ = compactMap.remove(key);
|
|
|
|
|
+ } else if (std.mem.eql(u8, opcode, "W")) {
|
|
|
|
|
+ compactMap.put(key, value) catch continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var compactedRecords = std.ArrayListUnmanaged([]const u8){};
|
|
|
|
|
+
|
|
|
|
|
+ var it = compactMap.iterator();
|
|
|
|
|
+ while (it.next()) |entry| {
|
|
|
|
|
+ const record = std.fmt.allocPrint(wal_allocator, "W|{s}|{s}", .{ entry.key_ptr.*, entry.value_ptr.* }) catch continue;
|
|
|
|
|
+ compactedRecords.append(wal_allocator, record) catch continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return compactedRecords.items;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+test "compaction test" {
|
|
|
|
|
+ const records = &[_][]const u8{
|
|
|
|
|
+ "W|key1|value1v1",
|
|
|
|
|
+ "W|key1|value1v2",
|
|
|
|
|
+ "W|key2|value2v1",
|
|
|
|
|
+ "D|key2|",
|
|
|
|
|
+ "W|key3|value3v1",
|
|
|
|
|
+ "W|key4|value4v1",
|
|
|
|
|
+ "D|key4|",
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const compactedExpected = &[_][]const u8{
|
|
|
|
|
+ "W|key1|value1v2",
|
|
|
|
|
+ "W|key3|value3v1",
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const compacted = compact(records[0..]);
|
|
|
|
|
+ for (compacted, 0..) |record, i| {
|
|
|
|
|
+ try std.testing.expectEqualStrings(compactedExpected[i], record);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|