╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭────────────────────────────────────────╮ │11 pub const event_id_size: usize = 32;│ │12 │ │13 pub const EventKind = enum { │ │14 + remove, │ │15 user, │ │16 repo, │ │17 issue, │ ╰────────────────────────────────────────╯ ╭────────────────────────────────────────────╮ │18 }; │ │19 │ │20 pub const EventData = union(EventKind) {│ │21 + remove: struct { │ │22 + event_kind: EventKind, │ │23 + }, │ │24 user: struct { │ │25 name: []const u8, │ │26 email: []const u8, │ ╰────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │45 kind: EventKind, │ │46 ) !EventData { │ │47 return switch (kind) { │ │48 + .remove => return error.InvalidEventKind, │ │49 .user => .{ │ │50 .user = .{ │ │51 .name = try readBytes(DB, hash_kind, allocator, map, "name"),│ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │361 _ = try std.fmt.hexToBytes(&current_event_id, &event.id); │ │362 │ │363 switch (event.data) { │ │364 + .remove => |data| { │ │365 + const removed_key = hash.hashInt(repo_opts.hash, &current_event_id); │ │366 + switch (data.event_kind) { │ │367 + .remove => return error.InvalidRemoveKind, │ │368 + .user => { │ │369 + const event_id_to_user_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "event-id->user")); │ │370 + const event_id_to_user = try DB.HashMap(.read_write).init(event_id_to_user_cursor); │ │371 + if (!try event_id_to_user.remove(removed_key)) return error.EventNotFound; │ │372 + │ │373 + const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "user-id->repos")); │ │374 + const user_id_to_repos = try DB.HashMap(.read_write).init(user_id_to_repos_cursor); │ │375 + _ = try user_id_to_repos.remove(removed_key); │ │376 + }, │ │377 + .repo => { │ │378 + const event_id_to_repo_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "event-id->repo")); │ │379 + const event_id_to_repo = try DB.HashMap(.read_write).init(event_id_to_repo_cursor); │ │380 + │ │381 + if (try event_id_to_repo.getCursor(removed_key)) |existing_repo_cursor| { │ │382 + const existing_repo_map = try DB.HashMap(.read_only).init(existing_repo_cursor); │ │383 + const existing_repo = try EventData.read(DB, repo_opts.hash, arena.allocator(), existing_repo_map, .repo);│ │384 + const payload = existing_repo.repo; │ │385 + │ │386 + const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "user-id->repos"));│ │387 + const user_id_to_repos = try DB.HashMap(.read_write).init(user_id_to_repos_cursor); │ │388 + │ │389 + const user_key = hash.hashInt(repo_opts.hash, payload.user_id); │ │390 + const user_repos_cursor = try user_id_to_repos.putCursor(user_key); │ │391 + const user_repos = try DB.CountedHashSet(.read_write).init(user_repos_cursor); │ │392 + _ = try user_repos.remove(removed_key); │ │393 + } │ │394 + if (!try event_id_to_repo.remove(removed_key)) return error.EventNotFound; │ │395 + }, │ │396 + .issue => { │ │397 + const event_id_to_issue_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "event-id->issue")); │ │398 + const event_id_to_issue = try DB.HashMap(.read_write).init(event_id_to_issue_cursor); │ │399 + if (!try event_id_to_issue.remove(removed_key)) return error.EventNotFound; │ │400 + }, │ │401 + } │ │402 + }, │ │403 .user => |data| { │ │404 const event_id_to_user_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "event-id->user")); │ │405 const event_id_to_user = try DB.HashMap(.read_write).init(event_id_to_user_cursor); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │406 │ │363 - const user_cursor = try event_id_to_user.putCursor(hash.hashInt(repo_opts.hash, &current_event_id));│ │407 + const user_key = hash.hashInt(repo_opts.hash, &current_event_id); │ │408 + const user_cursor = try event_id_to_user.putCursor(user_key); │ │409 const user_map = try DB.HashMap(.read_write).init(user_cursor); │ │365 - │ │410 try upsert(DB, repo_opts.hash, user_map, @TypeOf(data), data); │ │411 }, │ │412 .repo => |data| { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │413 const event_id_to_repo_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "event-id->repo"));│ │414 const event_id_to_repo = try DB.HashMap(.read_write).init(event_id_to_repo_cursor); │ │415 │ │372 - const repo_cursor = try event_id_to_repo.putCursor(hash.hashInt(repo_opts.hash, &current_event_id)); │ │416 + const repo_key = hash.hashInt(repo_opts.hash, &current_event_id); │ │417 + const repo_cursor = try event_id_to_repo.putCursor(repo_key); │ │418 const repo_map = try DB.HashMap(.read_write).init(repo_cursor); │ │374 - │ │419 try upsert(DB, repo_opts.hash, repo_map, @TypeOf(data), data); │ │420 │ │421 const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "user-id->repos"));│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │423 │ │424 const user_repos_cursor = try user_id_to_repos.putCursor(hash.hashInt(repo_opts.hash, data.user_id)); │ │425 const user_repos = try DB.CountedHashSet(.read_write).init(user_repos_cursor); │ │382 - try user_repos.put(hash.hashInt(repo_opts.hash, &current_event_id), .{ .bytes = &current_event_id }); │ │426 + try user_repos.put(repo_key, .{ .bytes = &current_event_id }); │ │427 }, │ │428 .issue => |data| { │ │429 const event_id_to_issue_cursor = try haxy_moment.putCursor(hash.hashInt(repo_opts.hash, "event-id->issue"));│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │430 const event_id_to_issue = try DB.HashMap(.read_write).init(event_id_to_issue_cursor); │ │431 │ │388 - const issue_cursor = try event_id_to_issue.putCursor(hash.hashInt(repo_opts.hash, &current_event_id));│ │432 + const issue_key = hash.hashInt(repo_opts.hash, &current_event_id); │ │433 + const issue_cursor = try event_id_to_issue.putCursor(issue_key); │ │434 const issue = try DB.HashMap(.read_write).init(issue_cursor); │ │390 - │ │435 try upsert(DB, repo_opts.hash, issue, @TypeOf(data), data); │ │436 }, │ │437 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/test/event.zig│ ╰──────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 942 try std.testing.expectEqual(1, try user_repos.count()); │ │ 943 │ │ 944 try std.testing.expect(null != try user_repos.getCursor(hash.hashInt(repo_opts.hash, &repo_event_id))); │ │ 945 + } │ │ 946 + │ │ 947 + // │ │ 948 + // remove the repo │ │ 949 + // │ │ 950 + │ │ 951 + const events_to_consume2 = [_]evt.Event{ │ │ 952 + .{ │ │ 953 + .id = std.fmt.bytesToHex(repo_event_id, .lower), │ │ 954 + .data = .{ │ │ 955 + .remove = .{ │ │ 956 + .event_kind = .repo, │ │ 957 + }, │ │ 958 + }, │ │ 959 + }, │ │ 960 + }; │ │ 961 + │ │ 962 + { │ │ 963 + var json: std.Io.Writer.Allocating = .init(std.testing.allocator); │ │ 964 + defer json.deinit(); │ │ 965 + │ │ 966 + for (events_to_consume2) |event| { │ │ 967 + json.clearRetainingCapacity(); │ │ 968 + │ │ 969 + try std.json.Stringify.value(event, .{}, &json.writer); │ │ 970 + │ │ 971 + // commit the event into a special branch │ │ 972 + _ = try repo.commitAtRef(io, allocator, .{ .message = json.written() }, null, .{ .kind = .head, .name = "haxy/meta" }); │ │ 973 + } │ │ 974 + } │ │ 975 + │ │ 976 + // │ │ 977 + // consume the removal into the database │ │ 978 + // │ │ 979 + │ │ 980 + { │ │ 981 + try evt.consume(repo_opts, io, allocator, &repo, .{ .kind = .head, .name = "haxy/meta" }); │ │ 982 + │ │ 983 + const history = try Repo.DB.ArrayList(.read_only).init(repo.core.db.rootCursor().readOnly()); │ │ 984 + │ │ 985 + // read the moment we just created │ │ 986 + const moment_cursor = try history.getCursor(-1) orelse return error.NotFound; │ │ 987 + const moment = try Repo.DB.HashMap(.read_only).init(moment_cursor); │ │ 988 + │ │ 989 + // get the last object id │ │ 990 + const last_object_id_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id")) orelse return error.NotFound; │ │ 991 + var last_object_id: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │ 992 + _ = try last_object_id_cursor.readBytes(&last_object_id); │ │ 993 + │ │ 994 + const haxy_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy")) orelse return error.NotFound; │ │ 995 + const haxy = try Repo.DB.ArrayList(.read_only).init(haxy_cursor); │ │ 996 + │ │ 997 + try std.testing.expectEqual(2, try haxy.count()); │ │ 998 + │ │ 999 + const haxy_moments_cursor = try haxy.getCursor(-1) orelse return error.NotFound; │ │1000 + const haxy_moments = try Repo.DB.HashMap(.read_only).init(haxy_moments_cursor); │ │1001 + │ │1002 + const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &last_object_id)) orelse return error.NotFound; │ │1003 + const haxy_moment = try Repo.DB.HashMap(.read_only).init(haxy_moment_cursor); │ │1004 + │ │1005 + // get the map of repos │ │1006 + const event_id_to_repo_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "event-id->repo")) orelse return error.NotFound;│ │1007 + const event_id_to_repo = try Repo.DB.HashMap(.read_only).init(event_id_to_repo_cursor); │ │1008 + │ │1009 + try std.testing.expect(null == try event_id_to_repo.getCursor(hash.hashInt(repo_opts.hash, &repo_event_id))); │ │1010 + │ │1011 + // get the repos created by the user │ │1012 + const user_id_to_repos_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "user-id->repos")) orelse return error.NotFound;│ │1013 + const user_id_to_repos = try Repo.DB.HashMap(.read_only).init(user_id_to_repos_cursor); │ │1014 + │ │1015 + const user_repos_cursor = try user_id_to_repos.getCursor(hash.hashInt(repo_opts.hash, &user_event_id)) orelse return error.NotFound; │ │1016 + const user_repos = try Repo.DB.CountedHashSet(.read_only).init(user_repos_cursor); │ │1017 + │ │1018 + try std.testing.expectEqual(0, try user_repos.count()); │ │1019 + try std.testing.expect(null == try user_repos.getCursor(hash.hashInt(repo_opts.hash, &repo_event_id))); │ │1020 + } │ │1021 + │ │1022 + // │ │1023 + // remove the user │ │1024 + // │ │1025 + │ │1026 + const events_to_consume3 = [_]evt.Event{ │ │1027 + .{ │ │1028 + .id = std.fmt.bytesToHex(user_event_id, .lower), │ │1029 + .data = .{ │ │1030 + .remove = .{ │ │1031 + .event_kind = .user, │ │1032 + }, │ │1033 + }, │ │1034 + }, │ │1035 + }; │ │1036 + │ │1037 + { │ │1038 + var json: std.Io.Writer.Allocating = .init(std.testing.allocator); │ │1039 + defer json.deinit(); │ │1040 + │ │1041 + for (events_to_consume3) |event| { │ │1042 + json.clearRetainingCapacity(); │ │1043 + │ │1044 + try std.json.Stringify.value(event, .{}, &json.writer); │ │1045 + │ │1046 + // commit the event into a special branch │ │1047 + _ = try repo.commitAtRef(io, allocator, .{ .message = json.written() }, null, .{ .kind = .head, .name = "haxy/meta" }); │ │1048 + } │ │1049 + } │ │1050 + │ │1051 + // │ │1052 + // consume the user removal into the database │ │1053 + // │ │1054 + │ │1055 + { │ │1056 + try evt.consume(repo_opts, io, allocator, &repo, .{ .kind = .head, .name = "haxy/meta" }); │ │1057 + │ │1058 + const history = try Repo.DB.ArrayList(.read_only).init(repo.core.db.rootCursor().readOnly()); │ │1059 + │ │1060 + // read the moment we just created │ │1061 + const moment_cursor = try history.getCursor(-1) orelse return error.NotFound; │ │1062 + const moment = try Repo.DB.HashMap(.read_only).init(moment_cursor); │ │1063 + │ │1064 + // get the last object id │ │1065 + const last_object_id_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id")) orelse return error.NotFound; │ │1066 + var last_object_id: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │1067 + _ = try last_object_id_cursor.readBytes(&last_object_id); │ │1068 + │ │1069 + const haxy_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy")) orelse return error.NotFound; │ │1070 + const haxy = try Repo.DB.ArrayList(.read_only).init(haxy_cursor); │ │1071 + │ │1072 + try std.testing.expectEqual(3, try haxy.count()); │ │1073 + │ │1074 + const haxy_moments_cursor = try haxy.getCursor(-1) orelse return error.NotFound; │ │1075 + const haxy_moments = try Repo.DB.HashMap(.read_only).init(haxy_moments_cursor); │ │1076 + │ │1077 + const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &last_object_id)) orelse return error.NotFound; │ │1078 + const haxy_moment = try Repo.DB.HashMap(.read_only).init(haxy_moment_cursor); │ │1079 + │ │1080 + // get the map of users │ │1081 + const event_id_to_user_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "event-id->user")) orelse return error.NotFound;│ │1082 + const event_id_to_user = try Repo.DB.HashMap(.read_only).init(event_id_to_user_cursor); │ │1083 + │ │1084 + try std.testing.expect(null == try event_id_to_user.getCursor(hash.hashInt(repo_opts.hash, &user_event_id))); │ │1085 } │ │1086 } │ │1087 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯